All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.spoon.mojo.RunMojo Maven / Gradle / Ivy

There is a newer version: 1.7.1
Show newest version
// Copyright 2012 Square, Inc.
package com.squareup.spoon.mojo;

import com.google.common.base.Strings;
import com.squareup.spoon.SpoonRunner;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;

import java.io.File;
import java.util.List;

import static com.squareup.spoon.SpoonRunner.DEFAULT_OUTPUT_DIRECTORY;
import static org.apache.maven.plugins.annotations.LifecyclePhase.INTEGRATION_TEST;

/**
 * Goal which invokes Spoon. By default the output will be placed in a {@code spoon-output/} folder
 * in your project's build directory.
 */
@SuppressWarnings("UnusedDeclaration") // Used reflectively by Maven.
@Mojo(name = "run", defaultPhase = INTEGRATION_TEST, threadSafe = false)
public class RunMojo extends AbstractSpoonMojo {
  private static final String SPOON_GROUP_ID = "com.squareup.spoon";
  private static final String SPOON_PLUGIN_ARTIFACT_ID = "spoon-maven-plugin";
  private static final String SPOON_RUNNER_ARTIFACT_ID = "spoon-runner";
  private static final String ARTIFACT_TYPE = "zip";
  private static final String ARTIFACT_CLASSIFIER = "spoon-output";

  /** {@code -Dmaven.test.skip} is commonly used with Maven to skip tests. We honor it too. */
  @Parameter(property = "maven.test.skip", defaultValue = "false", readonly = true)
  private boolean mavenTestSkip;

  /** {@code -DskipTests} is commonly used with Maven to skip tests. We honor it too. */
  @Parameter(property = "skipTests", defaultValue = "false", readonly = true)
  private boolean mavenSkipTests;

  /** Configuration option to skip execution. */
  @Parameter
  private boolean skip;

  /** A title for the output website. */
  @Parameter(defaultValue = "${project.name}")
  private String title;

  /** The location of the Android SDK. */
  @Parameter(defaultValue = "${env.ANDROID_HOME}")
  private String androidSdk;

  /** Attaches output artifact as zip when {@code true}. */
  @Parameter
  private boolean attachArtifact;

  /** If true then any test failures will cause the plugin to error. */
  @Parameter
  private boolean failOnFailure;

  /** If true then build will fail when no device to test on is attached. */
  @Parameter
  private boolean failIfNoDeviceConnected;

  /** Execute tests sequentially (one device at a time) */
  @Parameter
  private boolean sequential;

  /** Grant all runtime permissions during installation on Marshmallow and above devices */
  @Parameter
  private boolean grantAll;

  /** Whether debug logging is enabled. */
  @Parameter
  private boolean debug;

  @Parameter(property = "project.build.directory", required = true, readonly = true)
  private File buildDirectory;

  @Parameter(property = "project", required = true, readonly = true)
  private MavenProject project;

  /** Run only a specific test. */
  @Parameter(defaultValue = "${spoon.test.class}")
  private String className;

  /** Run only a specific test method.  Must be specified with {@link #className}. */
  @Parameter(defaultValue = "${spoon.test.method}")
  private String methodName;

  @Component
  private MavenProjectHelper projectHelper;

  @Component
  private RepositorySystem repositorySystem;

  @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
  private RepositorySystemSession repoSession;

  @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true)
  private List remoteRepositories;

  @Override public void execute() throws MojoExecutionException {
    Log log = getLog();

    if (mavenTestSkip || mavenSkipTests || skip) {
      log.debug("maven.test.skip = " + mavenTestSkip);
      log.debug("skipTests = " + mavenSkipTests);
      log.debug("skip = " + skip);
      log.info("Skipping Spoon execution.");
      return;
    }

    if (androidSdk == null) {
      throw new MojoExecutionException(
          "Could not find Android SDK. Ensure ANDROID_HOME environment variable is set.");
    }
    File sdkFile = new File(androidSdk);
    if (!sdkFile.exists()) {
      throw new MojoExecutionException(
          String.format("Could not find Android SDK at: %s", androidSdk));
    }
    log.debug("Android SDK: " + sdkFile.getAbsolutePath());

    File instrumentation = getInstrumentationApk();
    log.debug("Instrumentation APK: " + instrumentation);

    File app = getApplicationApk();
    log.debug("Application APK: " + app.getAbsolutePath());

    if (!Strings.isNullOrEmpty(className)) {
      log.debug("Class name: " + className);
      if (!Strings.isNullOrEmpty(methodName)) {
        log.debug("Method name: " + methodName);
      }
    }

    String classpath = getSpoonClasspath();
    log.debug("Classpath: " + classpath);

    log.debug("Output directory: " + outputDirectory.getAbsolutePath());

    log.debug("Spoon title: " + title);
    log.debug("Debug: " + Boolean.toString(debug));

    boolean success = new SpoonRunner.Builder() //
        .setTitle(title)
        .setApplicationApk(app)
        .setInstrumentationApk(instrumentation)
        .setOutputDirectory(outputDirectory)
        .setAndroidSdk(sdkFile)
        .setDebug(debug)
        .setClasspath(classpath)
        .setClassName(className)
        .setMethodName(methodName)
        .useAllAttachedDevices()
        .setFailIfNoDeviceConnected(failIfNoDeviceConnected)
        .setSequential(sequential)
        .setGrantAll(grantAll)
        .build()
        .run();

    if (!success && failOnFailure) {
      throw new MojoExecutionException("Spoon returned non-zero exit code.");
    }

    if (attachArtifact) {
      File outputZip = new File(buildDirectory, DEFAULT_OUTPUT_DIRECTORY + ".zip");
      ZipUtil.zip(outputZip, outputDirectory);
      projectHelper.attachArtifact(project, ARTIFACT_TYPE, ARTIFACT_CLASSIFIER, outputZip);
    }
  }

  private File getInstrumentationApk() throws MojoExecutionException {
    Artifact instrumentationArtifact = project.getArtifact();
    if (!"apk".equals(instrumentationArtifact.getType())) {
      throw new MojoExecutionException("Spoon can only be invoked on a module with type 'apk'.");
    }
    return aetherArtifact(instrumentationArtifact).getFile();
  }

  private File getApplicationApk() throws MojoExecutionException {
    for (Artifact dependency : project.getDependencyArtifacts()) {
      if ("apk".equals(dependency.getType())) {
        return aetherArtifact(dependency).getFile();
      }
    }
    throw new MojoExecutionException(
        "Could not find application. Ensure 'apk' dependency on it exists.");
  }

  private org.eclipse.aether.artifact.Artifact aetherArtifact(Artifact dep)
      throws MojoExecutionException {
    return resolveArtifact(dep.getGroupId(), dep.getArtifactId(), "apk", dep.getVersion());
  }

  private String getSpoonClasspath() throws MojoExecutionException {
    Log log = getLog();

    String spoonVersion = findMyVersion();
    log.debug("[getSpoonClasspath] Plugin version: " + spoonVersion);

    org.eclipse.aether.artifact.Artifact spoonRunner =
        resolveArtifact(SPOON_GROUP_ID, SPOON_RUNNER_ARTIFACT_ID, "jar", spoonVersion);
    log.debug("[getSpoonClasspath] Runner artifact: " + spoonRunner);

    return createClasspath(spoonRunner);
  }

  private String findMyVersion() throws MojoExecutionException {
    for (Artifact artifact : project.getPluginArtifacts()) {
      if (SPOON_GROUP_ID.equals(artifact.getGroupId()) //
          && SPOON_PLUGIN_ARTIFACT_ID.equals(artifact.getArtifactId())) {
        return artifact.getVersion();
      }
    }
    throw new MojoExecutionException("Could not find reference to Spoon plugin artifact.");
  }

  private org.eclipse.aether.artifact.Artifact resolveArtifact(String groupId, String artifactId,
      String extension, String version) throws MojoExecutionException {
    ArtifactRequest request = new ArtifactRequest();
    request.setArtifact(new DefaultArtifact(groupId, artifactId, extension, version));
    request.setRepositories(remoteRepositories);

    try {
      ArtifactResult artifactResult = repositorySystem.resolveArtifact(repoSession, request);
      return artifactResult.getArtifact();
    } catch (ArtifactResolutionException e) {
      throw new MojoExecutionException("Unable to resolve runner from repository.", e);
    }
  }

  private String createClasspath(org.eclipse.aether.artifact.Artifact artifact)
      throws MojoExecutionException {
    // Request a collection of all dependencies for the artifact.
    DependencyRequest dependencyRequest = new DependencyRequest();
    CollectRequest collectRequest = new CollectRequest();
    collectRequest.setRoot(new Dependency(artifact, ""));
    for (RemoteRepository remoteRepository : remoteRepositories) {
      collectRequest.addRepository(remoteRepository);
    }
    CollectResult result;
    try {
      result = repositorySystem.collectDependencies(repoSession, collectRequest);
    } catch (DependencyCollectionException e) {
      throw new MojoExecutionException("Unable to resolve runner dependencies.", e);
    }

    final Log log = getLog();
    final StringBuilder builder = new StringBuilder();

    // Walk the tree of all dependencies to add to the classpath.
    result.getRoot().accept(new DependencyVisitor() {
      @Override public boolean visitEnter(DependencyNode node) {
        log.debug("Visiting: " + node);

        // Resolve the dependency node artifact into a real, local artifact.
        org.eclipse.aether.artifact.Artifact resolvedArtifact;
        try {
          org.eclipse.aether.artifact.Artifact nodeArtifact = node.getDependency().getArtifact();
          resolvedArtifact =
              resolveArtifact(nodeArtifact.getGroupId(), nodeArtifact.getArtifactId(), "jar",
                  nodeArtifact.getVersion());
        } catch (MojoExecutionException e) {
          throw new RuntimeException(e);
        }

        // Add the artifact's path to our classpath.
        if (builder.length() > 0) {
          builder.append(File.pathSeparator);
        }
        builder.append(resolvedArtifact.getFile().getAbsolutePath());

        return true;
      }

      @Override public boolean visitLeave(DependencyNode node) {
        return true;
      }
    });

    return builder.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy