TestEventClientArgs.java

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.test.internal.events.client;

import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Provides arguments to {@link TestEventClient#connect(Context, TestEventClientConnectListener,
 * TestEventClientArgs)} to determine which Orchestrator service connections are needed, if any.
 *
 * <p>The {@link Builder#build()} call will determine which Orchestrator version to use depending on
 * which arguments are provided.
 */
public final class TestEventClientArgs {
  public final boolean isOrchestrated;
  public final boolean isPrimaryInstrProcess;
  public final boolean isTestDiscoveryRequested;
  public final boolean isTestRunEventsRequested;
  public final int orchestratorVersion;
  @Nullable public final String testDiscoveryService;
  @Nullable public final String testRunEventService;
  @Nullable public final ConnectionFactory connectionFactory;
  public final boolean testPlatformMigration;

  private TestEventClientArgs(
      boolean isOrchestrated, int orchestratorVersion, @NonNull Builder builder) {
    this.isOrchestrated = isOrchestrated;
    this.isPrimaryInstrProcess = builder.isPrimaryInstProcess;
    this.isTestDiscoveryRequested = builder.testDiscoveryRequested;
    this.isTestRunEventsRequested = builder.testRunEventsRequested;
    this.testDiscoveryService = builder.testDiscoveryService;
    this.testRunEventService = builder.testRunEventService;
    this.connectionFactory = builder.connectionFactory;
    this.orchestratorVersion = orchestratorVersion;
    this.testPlatformMigration = builder.testPlatformMigration;
  }

  /** Creates a new {@link TestEventClientArgs.Builder} instance. */
  @NonNull
  public static Builder builder() {
    return new Builder();
  }

  /**
   * Creates a new {@link TestEventClientArgs} instance using the provided arguments. Use the
   * applicable setters to configure the arguments then call {@link #build()} to create the new
   * {@link TestEventClientArgs}.
   */
  public static class Builder {
    private static final String TAG = "TestEventClient";
    boolean isPrimaryInstProcess = true;
    boolean testDiscoveryRequested = false;
    boolean testRunEventsRequested = false;
    boolean testPlatformMigration = false;
    @Nullable private ConnectionFactory connectionFactory;
    @Nullable private String orchestratorService;
    @Nullable private String testDiscoveryService;
    @Nullable private String testRunEventService;

    /**
     * Indicates whether this is the primary instrumentation process, as returned by {@code
     * MonitoringInstrumentation.isPrimaryInstrProcess(RunnerArgs)}.
     */
    @NonNull
    public Builder setPrimaryInstProcess(boolean isPrimaryInstProcess) {
      this.isPrimaryInstProcess = isPrimaryInstProcess;
      return this;
    }

    /**
     * If any non-empty value is present, then use Orchestrator v1 ({@code
     * androidx.test.orchestrator.OrchestratorService}) for test discovery or test run event
     * notifications. The actual value of {@link #orchestratorService} isn't used.
     *
     * <p>Requires one of {@link #testDiscoveryRequested} or {@link #testRunEventsRequested} to be
     * set to {@code true}.
     */
    @NonNull
    public Builder setOrchestratorService(@Nullable String orchestratorService) {
      this.orchestratorService = orchestratorService;
      return this;
    }

    /** Discover all available tests and send them back to the Orchestrator. */
    @NonNull
    public Builder setTestDiscoveryRequested(boolean discoveryRequested) {
      this.testDiscoveryRequested = discoveryRequested;
      return this;
    }

    /** Send test run status updates to the Orchestrator. */
    @NonNull
    public Builder setTestRunEventsRequested(boolean runEventsRequested) {
      this.testRunEventsRequested = runEventsRequested;
      return this;
    }

    /** Temporary workaround - should this use the new test platform event client */
    @NonNull
    public Builder setTestPlatformMigration(boolean testPlatformMigration) {
      this.testPlatformMigration = testPlatformMigration;
      return this;
    }

    /**
     * Discover available tests and send {@link
     * androidx.test.services.events.discovery.TestDiscoveryEvent}s back to the Orchestrator v2
     * {@code androidx.test.services.events.discovery.ITestDiscoveryEvent} service.
     *
     * <p>This implicitly sets {@link #testDiscoveryRequested} to {@code true}.
     */
    @NonNull
    public Builder setTestDiscoveryService(@Nullable String testDiscoveryService) {
      this.testDiscoveryService = testDiscoveryService;
      return this;
    }

    /**
     * Send {@link androidx.test.services.events.run.TestRunEvent}s back to the Orchestrator v2
     * {@code androidx.test.services.events.run.ITestRunEvent} service.
     *
     * <p>This implicitly sets {@link #testRunEventsRequested} to {@code true}.
     */
    @NonNull
    public Builder setTestRunEventService(@Nullable String testRunEventService) {
      this.testRunEventService = testRunEventService;
      return this;
    }

    /**
     * If a custom connectionFactory instance is provided, then {@link TestEventClient} will use
     * this factory to create the {@link TestEventServiceConnection}. This is used by {@code
     * AndroidJUnitRunner} to override the connection to use for the legacy v1 {@code
     * androidx.test.orchestrator.OrchestratorService}, i.e. when argument {@link
     * #orchestratorService} is provided.
     */
    @NonNull
    public Builder setConnectionFactory(@Nullable ConnectionFactory connectionFactory) {
      this.connectionFactory = connectionFactory;
      return this;
    }

    /**
     * Determines which operation to perform (test discovery, or test run event notifications) by
     * evaluating the provided arguments.
     *
     * @return a new {@link TestEventClientArgs} instance
     */
    @NonNull
    public TestEventClientArgs build() {
      int version = 0;
      if (testDiscoveryService != null && !testDiscoveryService.isEmpty()) {
        version = 2;
        testDiscoveryRequested = true;
        testRunEventsRequested = false;
      } else if (testRunEventService != null && !testRunEventService.isEmpty()) {
        version = 2;
        testRunEventsRequested = true;
        testDiscoveryRequested = false;
      } else if (orchestratorService != null) {
        if (connectionFactory == null) {
          Log.w(
              TAG,
              "Orchestrator service ["
                  + orchestratorService
                  + "] argument given, but no connectionFactory was provided for the v1 service");
        } else if (testDiscoveryRequested || testRunEventsRequested) {
          version = 1;
        } else {
          Log.w(
              TAG,
              "Orchestrator service ["
                  + orchestratorService
                  + "] argument given, but neither test discovery nor run event services "
                  + "was requested");
        }
      } else {
        Log.v(
            TAG,
            "No service name argument was given (testDiscoveryService, "
                + "testRunEventService or orchestratorService)");
        testDiscoveryRequested = false;
        testRunEventsRequested = false;
      }
      if (testDiscoveryRequested && testRunEventsRequested) {
        Log.w(TAG, "Can't use both the test discovery and run event services simultaneously");
        testRunEventsRequested = false;
      }
      if (version > 0) {
        Log.v(TAG, "Connecting to Orchestrator v" + version);
      }
      return new TestEventClientArgs(version > 0, version, this);
    }
  }

  /**
   * A client may optionally provide a factory for the remote service connection. This is required
   * when using the Orchestrator v1 service from {@code AndroidJUnitRunner}.
   */
  public interface ConnectionFactory {

    /**
     * Creates a new connection, e.g. {@code OrchestratorV1Connection} attached to the specified
     * listener.
     */
    @NonNull
    TestEventServiceConnection create(@NonNull TestEventClientConnectListener listener);
  }
}