Napisz Sharded IRemoteTest Runner

Podczas pisania programu do uruchamiania testów ważne jest, aby pomyśleć o skalowalności. Zadaj sobie pytanie: „jeśli mój biegacz testowy musiałby przeprowadzić 200 tys. przypadków testowych”, jak długo by to potrwało?

Sharding to jedna z odpowiedzi dostępnych w Federacji Handlowej. Wymaga podzielenia wszystkich testów, których potrzebuje biegacz, na kilka porcji, które można zrównoleglać.

Ta strona opisuje, jak sprawić, by Twój biegacz można było shardable dla Tradefed.

Interfejs do wdrożenia

Pojedynczym najważniejszym interfejsem do zaimplementowania, który ma być uważany za możliwy do shardowania przez TF, jest IShardableTest , który zawiera dwie metody: split(int numShard) i split() .

Jeśli Twoje fragmentowanie ma zależeć od liczby żądanych fragmentów, powinieneś zaimplementować split(int numShard) . W przeciwnym razie zaimplementuj split() .

Gdy polecenie TF test jest wykonywane z parametrami --shard-count i --shard-index , TF iteruje przez wszystkie IRemoteTest w celu wyszukania implementujących IShardableTest . Jeśli zostanie znaleziony, wywoła split , aby uzyskać nowy obiekt IRemoteTest w celu uruchomienia podzbioru przypadków testowych dla określonego fragmentu.

Co powinienem wiedzieć o podzielonym wdrożeniu?

  • Twój biegacz może shardować tylko pod pewnymi warunkami; w takim przypadku zwróć null , gdy nie dokonałeś fragmentacji.
  • Spróbuj podzielić tyle, ile ma to sens: podziel swojego biegacza na jednostkę wykonania, która ma dla niego sens. To naprawdę zależy od twojego biegacza. Na przykład: HostTest jest dzielony na fragmenty na poziomie klasy, każda klasa testowa jest umieszczana w osobnym fragmencie.
  • Jeśli ma to sens, dodaj kilka opcji, aby trochę kontrolować sharding. Na przykład: AndroidJUnitTest ma ajur-max-shard , aby określić maksymalną liczbę fragmentów, na które można go podzielić, niezależnie od żądanej liczby.

Szczegółowa przykładowa realizacja

Oto przykładowy fragment kodu implementujący IShardableTest , do którego możesz się odwołać. Pełny kod jest dostępny pod adresem (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;
    }
    ...
}

Ten przykład po prostu tworzy nowe wystąpienie samego siebie i ustawia dla niego parametry fragmentu. Jednak logika dzielenia może być zupełnie inna w zależności od testu; i dopóki jest deterministyczny i daje zbiorowo wyczerpujące podzbiory, jest w porządku.

Niezależność

Odłamki muszą być niezależne! Dwa fragmenty utworzone przez implementację split w programie uruchamiającym nie powinny być od siebie zależne ani udostępniać zasobów.

Dzielenie odłamków musi być deterministyczne! Jest to również obowiązkowe, biorąc pod uwagę te same warunki, metoda split powinna zawsze zwracać dokładnie tę samą listę fragmentów w tej samej kolejności.

UWAGA: Ponieważ każdy fragment może działać w różnych wystąpieniach TF, bardzo ważne jest, aby logika split dostarczała podzbiory, które wzajemnie się wykluczają i zbiorowo wyczerpują w sposób deterministyczny.

Jak shardować test lokalnie

Aby podzielić test na lokalny plik TF, możesz po prostu dodać opcję --shard-count do wiersza poleceń.

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

Wtedy TF automatycznie odrodzi komendy dla każdego odłamka i uruchomi je.

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)

Agregacja wyników testu

Ponieważ program TF nie wykonuje żadnej agregacji wyników testów dla wywołań podzielonych na fragmenty, należy upewnić się, że usługa raportowania obsługuje tę funkcję.