/* * Copyright 2015-present Facebook, Inc. * * 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 com.facebook.buck.jvm.java; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.java.runner.FileClassPathRunner; import com.facebook.buck.model.BuildId; import com.facebook.buck.test.selectors.TestSelectorList; import com.facebook.buck.util.Verbosity; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.collect.ImmutableList; import java.io.File; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Level; import org.immutables.value.Value; /** * Holds and formats the arguments and system properties that should be passed to the JVM to run * JUnit. */ @Value.Immutable @BuckStyleImmutable abstract class AbstractJUnitJvmArgs { private static final String BUILD_ID_PROPERTY = "com.facebook.buck.buildId"; private static final String MODULE_BASE_PATH_PROPERTY = "com.facebook.buck.moduleBasePath"; private static final String STD_OUT_LOG_LEVEL_PROPERTY = "com.facebook.buck.stdOutLogLevel"; private static final String STD_ERR_LOG_LEVEL_PROPERTY = "com.facebook.buck.stdErrLogLevel"; /** @return Directory to use to write test results to. */ abstract Optional<Path> getDirectoryForTestResults(); /** * @return Path to newline-separated file containing classpath entries with which to run the JVM. */ abstract Path getClasspathFile(); /** @return The type of test runner to use. */ abstract TestType getTestType(); /** * @return If true, suspend the JVM to allow a debugger to attach after launch. Defaults to false. */ @Value.Default boolean isDebugEnabled() { return false; } /** * @return If true, gathers code coverage metrics when running tests. * <p>Defaults to false. */ @Value.Default boolean isCodeCoverageEnabled() { return false; } /** * @return If true, passes inclNoLocationClassesEnabled=true to the jacoco java agent. * <p>Defaults to false. */ @Value.Default boolean isInclNoLocationClassesEnabled() { return false; } /** @return If true, include explanations for tests that were filtered out. */ @Value.Default boolean isShouldExplainTestSelectorList() { return false; } /** @return The filesystem path to a JVM agent (i.e., a profiler). */ abstract Optional<String> getPathToJavaAgent(); /** @return Unique identifier for the build. */ abstract BuildId getBuildId(); /** @return The filesystem path to the source code for the Buck module under test. */ abstract Path getBuckModuleBaseSourceCodePath(); /** * @return If set, specifies a minimum Java logging level at and above which java.util.logging * logs will be written to stdout. */ abstract Optional<Level> getStdOutLogLevel(); /** * @return If set, specifies a minimum Java logging level at and above which java.util.logging * logs will be written to stderr. */ abstract Optional<Level> getStdErrLogLevel(); /** * @return If set, specifies a filesystem path to which Robolectric debug logs will be written * during the test. */ abstract Optional<Path> getRobolectricLogPath(); /** @return The filesystem path to the compiled Buck test runner classes. */ abstract Path getTestRunnerClasspath(); /** @return Extra JVM args to pass on the command line. */ abstract Optional<ImmutableList<String>> getExtraJvmArgs(); /** @return Name of test classes to run. */ abstract ImmutableList<String> getTestClasses(); /** @return Test selectors with which to filter the tests to run. */ abstract Optional<TestSelectorList> getTestSelectorList(); /** Formats the JVM arguments in this object suitable to pass on the command line. */ public void formatCommandLineArgsToList( ImmutableList.Builder<String> args, ProjectFilesystem filesystem, Verbosity verbosity, long defaultTestTimeoutMillis) { // NOTE(agallagher): These probably don't belong here, but buck integration tests need // to find the test runner classes, so propagate these down via the relevant properties. args.add(String.format("-Dbuck.testrunner_classes=%s", getTestRunnerClasspath())); if (isCodeCoverageEnabled()) { args.add( String.format( "-javaagent:%s=destfile=%s/%s,append=true,inclnolocationclasses=%b", JacocoConstants.PATH_TO_JACOCO_AGENT_JAR, JacocoConstants.getJacocoOutputDir(filesystem), JacocoConstants.JACOCO_EXEC_COVERAGE_FILE, isInclNoLocationClassesEnabled())); } if (getPathToJavaAgent().isPresent()) { args.add(String.format("-agentpath:%s", getPathToJavaAgent().get())); } // Include the buildId args.add(String.format("-D%s=%s", BUILD_ID_PROPERTY, getBuildId())); // Include the baseDir args.add( String.format("-D%s=%s", MODULE_BASE_PATH_PROPERTY, getBuckModuleBaseSourceCodePath())); // Disable the Java icon from appearing in the OS X Dock while running tests args.add("-Dapple.awt.UIElement=true"); // Include log levels if (getStdOutLogLevel().isPresent()) { args.add(String.format("-D%s=%s", STD_OUT_LOG_LEVEL_PROPERTY, getStdOutLogLevel().get())); } if (getStdErrLogLevel().isPresent()) { args.add(String.format("-D%s=%s", STD_ERR_LOG_LEVEL_PROPERTY, getStdErrLogLevel().get())); } if (getRobolectricLogPath().isPresent()) { args.add(String.format("-Drobolectric.logging=%s", getRobolectricLogPath().get())); } if (isDebugEnabled()) { // This is the default config used by IntelliJ. By doing this, all a user // needs to do is create a new "Remote" debug config. Note that we start // suspended, so tests will not run until the user connects. args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"); } // User-defined VM arguments, such as -D or -X. if (getExtraJvmArgs().isPresent()) { args.addAll(getExtraJvmArgs().get()); } // verbose flag, if appropriate. if (verbosity.shouldUseVerbosityFlagIfAvailable()) { args.add("-verbose"); } args.add( "-classpath", "@" + filesystem.resolve(getClasspathFile()).toString() + File.pathSeparator + getTestRunnerClasspath().toString()); args.add(FileClassPathRunner.class.getName()); // Specify the Java class whose main() method should be run. This is the class that is // responsible for running the tests. args.add(getTestType().getDefaultTestRunner()); // The first argument to the test runner is where the test results should be written. It is not // reliable to write test results to stdout or stderr because there may be output from the unit // tests written to those file descriptors, as well. if (getDirectoryForTestResults().isPresent()) { args.add("--output", getDirectoryForTestResults().get().toString()); } // Add the default test timeout if --debug flag is not set long timeout = isDebugEnabled() ? 0 : defaultTestTimeoutMillis; args.add("--default-test-timeout", String.valueOf(timeout)); // Add the test selectors, one per line, in a single argument. StringBuilder selectorsArgBuilder = new StringBuilder(); if (getTestSelectorList().isPresent() && !getTestSelectorList().get().isEmpty()) { for (String rawSelector : getTestSelectorList().get().getRawSelectors()) { selectorsArgBuilder.append(rawSelector).append("\n"); } args.add("--test-selectors", selectorsArgBuilder.toString()); if (isShouldExplainTestSelectorList()) { args.add("--explain-test-selectors"); } } // List all of the tests to be run. for (String testClassName : getTestClasses()) { args.add(testClassName); } } }