diff --git a/java/execution/impl/src/com/intellij/testIntegration/DeadTestsCleaner.java b/java/execution/impl/src/com/intellij/testIntegration/DeadTestsCleaner.java new file mode 100644 index 000000000000..06432484a68c --- /dev/null +++ b/java/execution/impl/src/com/intellij/testIntegration/DeadTestsCleaner.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * 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.intellij.testIntegration; + +import com.intellij.execution.Location; +import com.intellij.execution.TestStateStorage; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Ref; + +import java.util.Collection; + +public class DeadTestsCleaner implements Runnable { + private final TestStateStorage myTestStorage; + private final Collection myTestUrls; + private final TestLocator myTestLocator; + + public DeadTestsCleaner(TestStateStorage storage, Collection urls, TestLocator locator) { + myTestStorage = storage; + myTestUrls = urls; + myTestLocator = locator; + } + + @Override + public void run() { + for (String url : myTestUrls) { + processUrl(url); + } + } + + private void processUrl(final String url) { + final Ref locationRef = Ref.create(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + Location location = myTestLocator.getLocation(url); + locationRef.set(location); + } + }); + if (locationRef.get() == null) { + myTestStorage.removeState(url); + } + } +} \ No newline at end of file diff --git a/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java b/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java index e23480c013d9..c453c16d222d 100644 --- a/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java +++ b/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java @@ -21,6 +21,7 @@ import com.intellij.execution.testframework.TestIconMapper; import com.intellij.execution.testframework.sm.runner.states.TestStateInfo; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.keymap.MacKeymapUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.ListPopupStep; @@ -74,6 +75,8 @@ public class ShowRecentTests extends AnAction { RecentTestsListPopup popup = new RecentTestsListPopup(selectStepTest, testRunner, testLocator); popup.showCenteredInCurrentWindow(project); + + ApplicationManager.getApplication().executeOnPooledThread(new DeadTestsCleaner(testStorage, urls, testLocator)); } private static Icon getIconFor(String value, Map records) { diff --git a/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt b/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt index a694583098ad..2189f6b46cbb 100644 --- a/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt +++ b/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt @@ -20,10 +20,7 @@ import com.intellij.execution.TestStateStorage import com.intellij.execution.testframework.sm.runner.states.TestStateInfo import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.testFramework.LightIdeaTestCase -import com.intellij.testIntegration.RecentTestRunner -import com.intellij.testIntegration.RecentTestsListProvider -import com.intellij.testIntegration.SelectTestStep -import com.intellij.testIntegration.TestLocator +import com.intellij.testIntegration.* import org.assertj.core.api.Assertions.assertThat import org.mockito.Matchers import org.mockito.Mockito.`when` @@ -35,7 +32,6 @@ fun failed(date: Date) = TestStateStorage.Record(TestStateInfo.Magnitude.FAILED_ class RecentTestsStepTest: LightIdeaTestCase() { val runner = mock(RecentTestRunner::class.java) - val testLocator = createLocator() val passed = passed(Date(0)) val failed = failed(Date(0)) @@ -43,14 +39,14 @@ class RecentTestsStepTest: LightIdeaTestCase() { class TestStorage { private val map: MutableMap = hashMapOf() - fun addSuite(name: String, pass: Boolean, date: Date = Date(0)) { + fun addSuite(name: String, pass: Boolean, date: Date = Date(0), language: String = "java") { val magnitude = if (pass) TestStateInfo.Magnitude.PASSED_INDEX else TestStateInfo.Magnitude.FAILED_INDEX - addSuite(name, magnitude, date) + addSuite(name, magnitude, date, language) } - fun addSuite(name: String, magnitude: TestStateInfo.Magnitude, date: Date = Date(0)) { + fun addSuite(name: String, magnitude: TestStateInfo.Magnitude, date: Date = Date(0), language: String = "java") { val record = TestStateStorage.Record(magnitude.value, date) - map.put("java:suite://$name", record) + map.put("$language:suite://$name", record) } fun addTest(name: String, magnitude: TestStateInfo.Magnitude, date: Date = Date(0)) { @@ -63,14 +59,10 @@ class RecentTestsStepTest: LightIdeaTestCase() { addTest(name, magnitude, date) } - fun addJsSuite(name: String, pass: Boolean, date: Date = Date(0)) { - val magnitude = if (pass) TestStateInfo.Magnitude.PASSED_INDEX else TestStateInfo.Magnitude.FAILED_INDEX - val record = TestStateStorage.Record(magnitude.value, date) - map.put("js:suite://$name", record) - } - fun getMap() = map + fun getSuite(name: String, language: String = "java") = map["$language:suite://$name"] + fun removeUrl(url: String) { map.remove(url) } @@ -145,11 +137,13 @@ class RecentTestsStepTest: LightIdeaTestCase() { fun `test show only java tests`() { val storage = TestStorage() + storage.addSuite("JavaTest1", true) storage.addSuite("JavaTest2", true) - storage.addJsSuite("JsSuite1", true) - storage.addJsSuite("JsSuite2", true) - storage.addJsSuite("JsSuite3", true) + + storage.addSuite("JsSuite1", true, Date(0), "js") + storage.addSuite("JsSuite2", true, Date(0), "js") + storage.addSuite("JsSuite3", true, Date(0), "js") val values = getSortedList(storage.getMap()) assertThat(values.map { VirtualFileManager.extractPath(it) }).isEqualTo(listOf("JavaTest1", "JavaTest2")) @@ -235,22 +229,76 @@ class RecentTestsStepTest: LightIdeaTestCase() { assertThat(values).isEqualTo(listOf("ASTest")) } - private fun createLocator(): TestLocator { + private fun locatorReturningNullIfContains(substring: String): TestLocator { val locator = mock(TestLocator::class.java) `when`(locator.getLocation(Matchers.anyString())).thenAnswer { val url = it.arguments[0] as String - if (url.contains("<")) null else mock(Location::class.java) + if (url.contains(substring)) null else mock(Location::class.java) } return locator } fun `test shown value without protocol`() { - val step = SelectTestStep(emptyList(), emptyMap(), runner, testLocator) + val step = SelectTestStep(emptyList(), emptyMap(), runner, mock(TestLocator::class.java)) var shownValue = step.getTextFor("java:suite://JavaFormatterSuperDuperTest") assertThat(shownValue).isEqualTo("JavaFormatterSuperDuperTest") shownValue = step.getTextFor("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt") assertThat(shownValue).isEqualTo("JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt") } - + + fun `test do not show urls which we can locate without location`() { + val storage = TestStorage() + + storage.addSuite("ASTest", true) + storage.addSuite("BSTest", false) + storage.addTest("BSTest.fff", false) + storage.addTest("BSTest.ppp", true) + + storage.addTest("", false) + storage.addSuite("", false) + + val testStorageMock = createMockStorage(storage) + + val map = storage.getMap() + + val cleaner = DeadTestsCleaner(testStorageMock, map.keys.toList(), locatorReturningNullIfContains("<")) + cleaner.run() + + val sortedUrlList = getSortedList(storage.getMap()) + val values = sortedUrlList.map { VirtualFileManager.extractPath(it) } + + assertThat(values).isEqualTo(listOf("BSTest.fff", "BSTest", "ASTest")) + assertThat(storage.getSuite("")).isEqualTo(null) + } + + fun `test do not remove tests if we are unable to locate them`() { + val storage = TestStorage() + + storage.addSuite("ASTest", true) + + storage.addSuite("JsSuite1", true, Date(), "js") + storage.addSuite("JsSuite2", true, Date(), "js") + + val testStorageMock = createMockStorage(storage) + + val sortedUrlList = getSortedList(storage.getMap()) + + val cleaner = DeadTestsCleaner(testStorageMock, sortedUrlList, locatorReturningNullIfContains("JsSuite")) + cleaner.run() + + assertThat(storage.getSuite("JsSuite1", "js")).isNotEqualTo(null) + assertThat(storage.getSuite("JsSuite2", "js")).isNotEqualTo(null) + } + + private fun createMockStorage(storage: TestStorage): TestStateStorage? { + val testStorageMock = mock(TestStateStorage::class.java) + `when`(testStorageMock.removeState(Matchers.anyString())).then { + val url = it.arguments[0]as String + storage.removeUrl(url) + } + return testStorageMock + } + + } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java b/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java index 4875cf49117a..5826d53ac6f4 100644 --- a/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java +++ b/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java @@ -131,6 +131,17 @@ public class TestStateStorage implements Disposable { } } + public synchronized void removeState(String url) { + if (myMap != null) { + try { + myMap.remove(url); + } + catch (IOException e) { + thingsWentWrongLetsReinitialize(e, "Can't remove state for " + url); + } + } + } + @Nullable public synchronized Map getRecentTests(int limit, Date since) { if (myMap == null) return null;