PlatformTestStorage.java

/*
 * Copyright (C) 2021 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.platform.io;

import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Map;

/**
 * An interface representing on-device I/O operations in an Android test.
 *
 * <p>This API allows users to retrieve test data specified in the build configuration, and write
 * output test data that can be automatically collected by the test runner infrastructure, if the
 * environment supports it.
 *
 * <p>Typically users will retrieve the appropriate implementation via {@link
 * PlatformTestStorageRegistry#getInstance()}.
 *
 * <p>Implementers would need to also implement the appropriate test runner support for pushing and
 * pulling the test data to and from the device from the build environment.
 */
public interface PlatformTestStorage {

  /**
   * Provides an InputStream to a test file dependency.
   *
   * <p>In bazel/blaze environments, this corresponds to files passed in the 'data' attribute of the
   * android_instrumentation_test or android_local_test build rule.
   *
   * <p>This API is currently not supported in gradle environments.
   *
   * @param pathname the path to the test file dependency, relative to the root where the storage
   *     implementation stores input files. Should not be null.
   * @return a potentially unbuffered InputStream to the given test file. Users will typically want
   *     to buffer the input in memory when reading from this stream.
   * @throws FileNotFoundException if pathname does not exist
   */
  InputStream openInputFile(String pathname) throws FileNotFoundException;

  /**
   * Returns the value of a given argument name.
   *
   * <p>In bazel/blaze environments, this corresponds to flags passed in the 'args' attribute of the
   * android_instrumentation_test or android_local_test build rule.
   *
   * <p>This API is currently unsupported in gradle environments. It is recommended to use {@link
   * InstrumentationRegistry#getArguments()} as an alternative.
   *
   * @param argName the argument name. Should not be null.
   */
  String getInputArg(String argName);

  /**
   * Returns the name/value map of all test arguments or an empty map if no arguments are defined.
   *
   * @see {@link #getInputArg(String)}
   */
  Map<String, String> getInputArgs();

  /**
   * Provides an OutputStream to a test output file. Will overwrite any data written to the same
   * pathname in the same test run.
   *
   * <p>Supported test runners will pull the files from the device once the test completes.
   *
   * <p>In gradle android instrumentation test environments, the files will typically be stored in
   * path_to_your_project/module_name/build/outputs/managed_device_android_test_additional_output
   * <br>
   *
   * @param pathname relative path to the test output file. Should not be null.
   * @return a potentially unbuffered OutputStream to the given output file. Users will typically
   *     want to buffer the output in memory when writing to this stream.
   * @throws FileNotFoundException if pathname does not exist
   */
  OutputStream openOutputFile(String pathname) throws FileNotFoundException;

  /**
   * Provides an OutputStream to a test output file.
   *
   * <p>This API is identical to {@link #openOutputFile(String)} with the additional feature of
   * allowing appending or overwriting test data.
   *
   * @param pathname path to the test output file. Should not be null.
   * @param append if true, then the lines will be added to the end of the file rather than
   *     overwriting.
   * @return an OutputStream to the given output file.
   * @throws FileNotFoundException if pathname does not exist
   */
  OutputStream openOutputFile(String pathname, boolean append) throws FileNotFoundException;

  /**
   * Adds the given properties.
   *
   * <p>Adding a property with the same name would append new values and overwrite the old values if
   * keys already exist.
   *
   * <p>This API is unsupported in gradle environments. <br>
   */
  void addOutputProperties(Map<String, Serializable> properties);

  /**
   * Returns a map of all the output test properties. If no properties exist, an empty map will be
   * returned.
   */
  Map<String, Serializable> getOutputProperties();

  /**
   * Provides an InputStream to an internal file used by the testing infrastructure.
   *
   * @param pathname path to the internal file. Should not be null.
   * @return an InputStream to the given test file.
   * @throws FileNotFoundException if pathname does not exist
   * @hide
   */
  @RestrictTo(Scope.LIBRARY_GROUP)
  default InputStream openInternalInputFile(String pathname) throws FileNotFoundException {
    return openInputFile(pathname);
  }

  /**
   * Provides an OutputStream to an internal file used by the testing infrastructure.
   *
   * @param pathname path to the internal file. Should not be null.
   * @return an OutputStream to the given output file.
   * @throws FileNotFoundException if pathname does not exist
   * @hide
   */
  @RestrictTo(Scope.LIBRARY_GROUP)
  default OutputStream openInternalOutputFile(String pathname) throws FileNotFoundException {
    return openOutputFile(pathname);
  }

  /**
   * Provides a Uri to a test file dependency.
   *
   * <p>In most of the cases, you would use {@link #openInputFile(String)} for opening up an
   * InputStream to the input file content immediately. Only use this method if you would like to
   * store the file Uri and use it for I/O operations later.
   *
   * @param pathname path to the test file dependency. Should not be null. This is a relative path
   *     to where the storage implementation stores the input files. For example, if the storage
   *     service stores the input files under "/sdcard/test_input_files", with a pathname
   *     "/path/to/my_input.txt", the file will end up at
   *     "/sdcard/test_input_files/path/to/my_input.txt" on device.
   * @return a content Uri to the test file dependency.
   *     <p>Note: temporary API - will be renamed to getInputFileUri in future
   */
  Uri getInputFileUri(@NonNull String pathname);

  /**
   * Provides a Uri to a test output file.
   *
   * <p>In most of the cases, you would use {@link #openOutputFile(String)} for opening up an
   * OutputStream to the output file content immediately. Only use this method if you would like to
   * store the file Uri and use it for I/O operations later.
   *
   * @param pathname path to the test output file. Should not be null. This is a relative path to
   *     where the storage implementation stores the output files. For example, if the storage
   *     service stores the output files under "/sdcard/test_output_files", with a pathname
   *     "/path/to/my_output.txt", the file will end up at
   *     "/sdcard/test_output_files/path/to/my_output.txt" on device.
   */
  Uri getOutputFileUri(@NonNull String pathname);

  /**
   * Returns true if {@code pathname} corresponds to a file or directory that is in a directory
   * where the storage implementation stores files.
   *
   * @param pathname path to a file or directory. Should not be null. This is an absolute path to a
   *     file that may be a part of the storage service.
   */
  boolean isTestStorageFilePath(@NonNull String pathname);
}