blob: 9974ab39734165b663d55e52b13e1886e788f16b [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.tools.idea.testing
import com.android.testutils.VirtualTimeScheduler
import com.android.tools.analytics.LoggedUsage
import com.android.tools.analytics.TestUsageTracker
import com.android.tools.analytics.TestUsageTrackerListener
import com.android.tools.analytics.UsageTracker
import com.android.tools.idea.gradle.project.build.invoker.GradleBuildInvoker
import com.android.tools.idea.gradle.project.build.invoker.GradleInvocationResult
import com.android.tools.idea.testartifacts.TestConfigurationTesting.createContext
import com.google.common.truth.Truth.assertThat
import com.google.wireless.android.sdk.stats.AndroidStudioEvent
import com.intellij.execution.ExecutionListener
import com.intellij.execution.ExecutionManager
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.impl.ExecutionManagerImpl
import com.intellij.execution.junit.JUnitConfiguration
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ExecutionUtil
import com.intellij.execution.testframework.TestSearchScope
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
import com.intellij.execution.testframework.sm.runner.SMTestProxy
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.testFramework.TestRunnerUtil
import com.intellij.testFramework.runInEdtAndWait
import org.junit.Ignore
import java.util.concurrent.ConcurrentSkipListSet
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicReference
/**
* Whether to print debug "status updates" to stdout.
*
* This may be useful when debugging, since the steps in this test can take quite some time.
*/
private const val PRINT_LOG = false
private fun log(message: String) {
@Suppress("ConstantConditionIf")
if (PRINT_LOG) {
println(message)
}
}
/**
* Integration test that invokes our unit test run configurations.
*
* Test code is executed on the main thread, not EDT. This way we can block waiting for testing to finish.
*
* The test project uses 3.0 features, so you may need to set STUDIO_CUSTOM_REPO to point to a recent build of our gradle plugin.
*/
@Ignore
// TODO(karimai): Re-enable in a separate dedicated change.
class UnitTestingSupportIntegrationTest : AndroidGradleTestCase() {
override fun runInDispatchThread(): Boolean = false
override fun invokeTestRunnable(runnable: Runnable) = runnable.run()
override fun setUp() {
TestRunnerUtil.replaceIdeEventQueueSafely() // See UsefulTestCase#runBare which should be the stack frame above this one.
runInEdtAndWait {
super.setUp()
loadProject(TestProjectPaths.UNIT_TESTING)
// Without this, the execution manager will not invoke gradle to compile the project, so no tests will be found.
val executionManager = ExecutionManagerImpl.getInstance(project)
log("project imported")
// Calling the code below makes sure there are no mistakes in the test project and that all class files are in place. Doing this
// (and syncing VFS) seems to fix huge flakiness that we observe otherwise.
// TODO(b/64667992): try to listen for a finished gradle build (the one from "run before" in the run config) and sync VFS then.
val result = invokeGradleTasks(project, "test")
assertTrue("Gradle build failed.", result.isBuildSuccessful)
log("Command-line tests done")
VirtualFileManager.getInstance().syncRefresh()
log("Vfs synced")
}
}
override fun tearDown() {
try {
UsageTracker.cleanAfterTesting()
}
finally {
super.tearDown()
}
}
fun testAppModule() {
checkTestClass(
"com.example.app.AppJavaUnitTest",
expectedTests = setOf(
"java:test://com.example.app.AppJavaUnitTest/aarDependencies",
"java:test://com.example.app.AppJavaUnitTest/assertions",
"java:test://com.example.app.AppJavaUnitTest/commonsLogging",
"java:test://com.example.app.AppJavaUnitTest/enums",
"java:test://com.example.app.AppJavaUnitTest/exceptions",
"java:test://com.example.app.AppJavaUnitTest/instanceFields",
"java:test://com.example.app.AppJavaUnitTest/javaLibJavaResourcesOnClasspath",
"java:test://com.example.app.AppJavaUnitTest/javaResourcesOnClasspath",
"java:test://com.example.app.AppJavaUnitTest/libJavaResourcesOnClasspath",
"java:test://com.example.app.AppJavaUnitTest/mockFinalClass",
"java:test://com.example.app.AppJavaUnitTest/mockFinalMethod",
"java:test://com.example.app.AppJavaUnitTest/mockInnerClass",
"java:test://com.example.app.AppJavaUnitTest/prodJavaResourcesOnClasspath",
"java:test://com.example.app.AppJavaUnitTest/prodRClass",
"java:test://com.example.app.AppJavaUnitTest/referenceJavaLibJavaClass",
"java:test://com.example.app.AppJavaUnitTest/referenceJavaLibKotlinClass",
"java:test://com.example.app.AppJavaUnitTest/referenceLibraryCode",
"java:test://com.example.app.AppJavaUnitTest/referenceLibraryKotlinCode",
"java:test://com.example.app.AppJavaUnitTest/referenceProductionCode",
"java:test://com.example.app.AppJavaUnitTest/referenceProductionKotlinCode",
"java:test://com.example.app.AppJavaUnitTest/workingDir"
)
)
checkTestClass(
"com.example.app.AppKotlinUnitTest",
expectedTests = setOf(
"java:test://com.example.app.AppKotlinUnitTest/aarDependencies",
"java:test://com.example.app.AppKotlinUnitTest/assertions",
"java:test://com.example.app.AppKotlinUnitTest/commonsLogging",
"java:test://com.example.app.AppKotlinUnitTest/enums",
"java:test://com.example.app.AppKotlinUnitTest/exceptions",
"java:test://com.example.app.AppKotlinUnitTest/instanceFields",
"java:test://com.example.app.AppKotlinUnitTest/javaLibJavaResourcesOnClasspath",
"java:test://com.example.app.AppKotlinUnitTest/javaResourcesOnClasspath",
"java:test://com.example.app.AppKotlinUnitTest/libJavaResourcesOnClasspath",
"java:test://com.example.app.AppKotlinUnitTest/mockFinalClass",
"java:test://com.example.app.AppKotlinUnitTest/mockFinalMethod",
"java:test://com.example.app.AppKotlinUnitTest/mockInnerClass",
"java:test://com.example.app.AppKotlinUnitTest/prodJavaResourcesOnClasspath",
"java:test://com.example.app.AppKotlinUnitTest/prodRClass",
"java:test://com.example.app.AppKotlinUnitTest/referenceJavaLibJavaClass",
"java:test://com.example.app.AppKotlinUnitTest/referenceJavaLibKotlinClass",
"java:test://com.example.app.AppKotlinUnitTest/referenceLibraryCode",
"java:test://com.example.app.AppKotlinUnitTest/referenceLibraryKotlinCode",
"java:test://com.example.app.AppKotlinUnitTest/referenceProductionCode",
"java:test://com.example.app.AppKotlinUnitTest/referenceProductionKotlinCode",
"java:test://com.example.app.AppKotlinUnitTest/workingDir"
)
)
}
fun testLibModule() {
checkTestClass(
"com.example.lib.LibJavaUnitTest",
expectedTests = setOf(
"java:test://com.example.lib.LibJavaUnitTest/aarDependencies",
"java:test://com.example.lib.LibJavaUnitTest/assertions",
"java:test://com.example.lib.LibJavaUnitTest/commonsLogging",
"java:test://com.example.lib.LibJavaUnitTest/enums",
"java:test://com.example.lib.LibJavaUnitTest/exceptions",
"java:test://com.example.lib.LibJavaUnitTest/instanceFields",
"java:test://com.example.lib.LibJavaUnitTest/javaLibJavaResourcesOnClasspath",
"java:test://com.example.lib.LibJavaUnitTest/javaResourcesOnClasspath",
"java:test://com.example.lib.LibJavaUnitTest/libJavaResourcesOnClasspath",
"java:test://com.example.lib.LibJavaUnitTest/mockFinalClass",
"java:test://com.example.lib.LibJavaUnitTest/mockFinalMethod",
"java:test://com.example.lib.LibJavaUnitTest/mockInnerClass",
"java:test://com.example.lib.LibJavaUnitTest/prodJavaResourcesOnClasspath",
"java:test://com.example.lib.LibJavaUnitTest/prodRClass",
"java:test://com.example.lib.LibJavaUnitTest/referenceJavaLibJavaClass",
"java:test://com.example.lib.LibJavaUnitTest/referenceJavaLibKotlinClass",
"java:test://com.example.lib.LibJavaUnitTest/referenceLibraryCode",
"java:test://com.example.lib.LibJavaUnitTest/referenceLibraryKotlinCode",
"java:test://com.example.lib.LibJavaUnitTest/referenceProductionCode",
"java:test://com.example.lib.LibJavaUnitTest/referenceProductionKotlinCode",
"java:test://com.example.lib.LibJavaUnitTest/workingDir"
)
)
checkTestClass(
"com.example.lib.LibKotlinUnitTest",
expectedTests = setOf(
"java:test://com.example.lib.LibKotlinUnitTest/aarDependencies",
"java:test://com.example.lib.LibKotlinUnitTest/assertions",
"java:test://com.example.lib.LibKotlinUnitTest/commonsLogging",
"java:test://com.example.lib.LibKotlinUnitTest/enums",
"java:test://com.example.lib.LibKotlinUnitTest/exceptions",
"java:test://com.example.lib.LibKotlinUnitTest/instanceFields",
"java:test://com.example.lib.LibKotlinUnitTest/javaLibJavaResourcesOnClasspath",
"java:test://com.example.lib.LibKotlinUnitTest/javaResourcesOnClasspath",
"java:test://com.example.lib.LibKotlinUnitTest/libJavaResourcesOnClasspath",
"java:test://com.example.lib.LibKotlinUnitTest/mockFinalClass",
"java:test://com.example.lib.LibKotlinUnitTest/mockFinalMethod",
"java:test://com.example.lib.LibKotlinUnitTest/mockInnerClass",
"java:test://com.example.lib.LibKotlinUnitTest/prodJavaResourcesOnClasspath",
"java:test://com.example.lib.LibKotlinUnitTest/prodRClass",
"java:test://com.example.lib.LibKotlinUnitTest/referenceJavaLibJavaClass",
"java:test://com.example.lib.LibKotlinUnitTest/referenceJavaLibKotlinClass",
"java:test://com.example.lib.LibKotlinUnitTest/referenceLibraryCode",
"java:test://com.example.lib.LibKotlinUnitTest/referenceLibraryKotlinCode",
"java:test://com.example.lib.LibKotlinUnitTest/referenceProductionCode",
"java:test://com.example.lib.LibKotlinUnitTest/referenceProductionKotlinCode",
"java:test://com.example.lib.LibKotlinUnitTest/workingDir"
)
)
}
fun testJavaLibModule() {
checkTestClass(
"com.example.javalib.JavaLibJavaTest",
expectedTests = setOf(
"java:test://com.example.javalib.JavaLibJavaTest/assertions",
"java:test://com.example.javalib.JavaLibJavaTest/javaResourcesOnClasspath",
"java:test://com.example.javalib.JavaLibJavaTest/prodJavaResourcesOnClasspath",
"java:test://com.example.javalib.JavaLibJavaTest/referenceJavaLibJavaClass",
"java:test://com.example.javalib.JavaLibJavaTest/referenceJavaLibKotlinClass",
"java:test://com.example.javalib.JavaLibJavaTest/workingDir"
)
)
// TODO(b/64667992): check JavaLibKotlinTest once the Kotlin setup works.
}
/**
* Tries to test that running all tests "across module boundaries" does the right thing. Unfortunately this cannot really be done, so
* instead we check the right Gradle tasks are executed and that enough directories are on the classpath of the test VM. See the comment
* in [ExecutionListener] below for details.
*/
fun testAcrossModuleBoundaries() {
val gradleBuildInvoker = GradleBuildInvoker.getInstance(project)
gradleBuildInvoker.add(object : GradleBuildInvoker.AfterGradleInvocationTask {
override fun execute(result: GradleInvocationResult) {
gradleBuildInvoker.remove(this)
assertThat(result.tasks).containsAllOf(
":app:compileDebugUnitTestSources",
":util-lib:compileDebugUnitTestSources",
":javalib:testClasses"
)
}
})
project.messageBus.connect(testRootDisposable).subscribe(ExecutionManager.EXECUTION_TOPIC, object : ExecutionListener {
override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
// The way testing "all in package" typically works is that a testing VM is started with paths to some temporary helper files. These
// files contain tests to be executed as well as the desired working directory, classpath etc. For some reason, there's a mechanism
// in place in JUnitStarter that will read byte from a local socket before opening the files, presumably to make sure the files have
// been updated with correct content (the socket's port number is passed on the command line to the JUnitStarter VM). TestPackage
// generally uses this mechanism, so in `createJavaParameters` it adds "-socket1234" to the test VM command line. Unfortunately,
// in unit test mode it never actually writes to the socket (see
// [com.intellij.execution.testframework.SearchForTestsTask#startSearch]), so we end up with a deadlock: the IDE process is waiting
// for testing to finish and the testing VM is waiting on the IDE to signal readiness. Because of that we just kill the test
// process here, as the most we can check is that correct Gradle tasks have been run.
handler.destroyProcess()
}
})
runInEdtAndWait {
val runnerConfigurationSettings = createRunnerConfigurationSettingsForClass("com.example.app.AppJavaUnitTest")
val androidJUnit = runnerConfigurationSettings.configuration as JUnitConfiguration
androidJUnit.persistentData.TEST_OBJECT = JUnitConfiguration.TEST_PACKAGE
androidJUnit.persistentData.TEST_SEARCH_SCOPE.scope = TestSearchScope.MODULE_WITH_DEPENDENCIES
androidJUnit.persistentData.MAIN_CLASS_NAME = ""
androidJUnit.persistentData.PACKAGE_NAME = ""
assertThat(androidJUnit.testObject.suggestActionName()).isEqualTo("All Tests")
ExecutionUtil.runConfiguration(runnerConfigurationSettings, DefaultRunExecutor.getRunExecutorInstance())
}
}
/**
* Checks that an [com.android.tools.idea.testartifacts.junit.AndroidTestObject] wrapping an
* [com.android.tools.idea.testartifacts.junit.AndroidTestsPattern] doesn't fail in `suggestActionName`. This code path is hit when
* right-clicking res nodes in the Android project view.
*/
fun testPatternTestObject() = runInEdtAndWait {
val runnerConfigurationSettings = createRunnerConfigurationSettingsForClass("com.example.app.AppJavaUnitTest")
val androidJUnit = runnerConfigurationSettings.configuration as JUnitConfiguration
androidJUnit.persistentData.TEST_OBJECT = JUnitConfiguration.TEST_PATTERN
androidJUnit.persistentData.TEST_SEARCH_SCOPE.scope = TestSearchScope.MODULE_WITH_DEPENDENCIES
androidJUnit.persistentData.MAIN_CLASS_NAME = ""
androidJUnit.persistentData.PACKAGE_NAME = ""
assertThat(androidJUnit.testObject.suggestActionName()).isNull()
}
private fun checkTestClass(className: String, expectedTests: Set<String>) {
val passingTests = ConcurrentSkipListSet<String>()
val failingTests = ConcurrentSkipListSet<String>()
val testingFinished = CountDownLatch(2)
val failure = AtomicReference<Throwable>()
val tracker = TestUsageTracker(VirtualTimeScheduler())
tracker.listener = object: TestUsageTrackerListener {
override fun onNewUsage(loggedUsage: LoggedUsage) {
if (loggedUsage.studioEvent.kind == AndroidStudioEvent.EventKind.TEST_RUN) {
testingFinished.countDown()
}
}
}
UsageTracker.setWriterForTest(tracker)
runInEdtAndWait {
try {
project.messageBus.connect(testRootDisposable).subscribe(SMTRunnerEventsListener.TEST_STATUS, object : SMTRunnerEventsAdapter() {
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
log("Testing $className started.")
}
override fun onTestStarted(test: SMTestProxy) {
log("${test.name} started")
}
override fun onTestFinished(test: SMTestProxy) {
log("${test.name} finished")
val set = if (test.isPassed) passingTests else failingTests
set.add(test.locationUrl ?: return)
}
override fun onTestingFinished(testsRoot: SMTestProxy.SMRootTestProxy) {
log("Testing $className finished.")
testingFinished.countDown()
}
})
log("Running $className")
ExecutionUtil.runConfiguration(createRunnerConfigurationSettingsForClass(className), DefaultRunExecutor.getRunExecutorInstance())
}
catch (t: Throwable) {
log("failed!")
failure.set(t)
testingFinished.countDown()
}
}
// Make sure we don't hang the entire build here.
/* b/154963507
assertTrue("Timed out", testingFinished.await(1, TimeUnit.MINUTES))
failure.get()?.let { throw it }
log("Checking $className")
assertThat(passingTests).containsExactlyElementsIn(expectedTests)
assertThat(failingTests).isEmpty()
val events = tracker.usages
.map { it.studioEvent }
.filter { it.kind == AndroidStudioEvent.EventKind.TEST_RUN }
assertThat(events).hasSize(1)
val testRun = events.single().testRun!!
assertThat(testRun.testInvocationType).isEqualTo(TestRun.TestInvocationType.ANDROID_STUDIO_TEST)
assertThat(testRun.testKind).isEqualTo(TestRun.TestKind.UNIT_TEST)
assertThat(testRun.numberOfTestsExecuted).isEqualTo(expectedTests.size)
assertThat(testRun.testLibraries.mockitoVersion).isEqualTo("2.7.1")
b/154963507 */
}
private fun createRunnerConfigurationSettingsForClass(className: String): RunnerAndConfigurationSettings {
return createContext(project, myFixture.findClass(className)).configuration!!
}
}