diff --git a/java/execution/impl/src/com/intellij/testIntegration/LocationTestRunner.java b/java/execution/impl/src/com/intellij/testIntegration/LocationTestRunner.java new file mode 100644 index 000000000000..e75cab71b4ef --- /dev/null +++ b/java/execution/impl/src/com/intellij/testIntegration/LocationTestRunner.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2015 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.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +public interface LocationTestRunner { + enum Mode { RUN, DEBUG } + void setMode(Mode mode); + void run(Location location); +} + +class LocationTestRunnerImpl implements LocationTestRunner { + private static AnAction RUN = ActionManager.getInstance().getAction("RunClass"); + private static AnAction DEBUG = ActionManager.getInstance().getAction("DebugClass"); + + protected AnAction myCurrentAction = DEBUG; + + public void setMode(Mode mode) { + switch (mode) { + case RUN: + myCurrentAction = RUN; + break; + case DEBUG: + myCurrentAction = DEBUG; + break; + } + } + + public void run(final Location location) { + DataContext data = new DataContext() { + @Nullable + @Override + public Object getData(@NonNls String dataId) { + if (Location.DATA_KEY.is(dataId)) { + return location; + } + return null; + } + }; + + myCurrentAction.actionPerformed(AnActionEvent.createFromAnAction(myCurrentAction, null, "", data)); + } +} diff --git a/java/execution/impl/src/com/intellij/testIntegration/SelectTestStep.java b/java/execution/impl/src/com/intellij/testIntegration/SelectTestStep.java new file mode 100644 index 000000000000..c73897cd7d87 --- /dev/null +++ b/java/execution/impl/src/com/intellij/testIntegration/SelectTestStep.java @@ -0,0 +1,100 @@ +/* + * Copyright 2000-2015 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.execution.testframework.JavaTestLocator; +import com.intellij.execution.testframework.TestIconMapper; +import com.intellij.execution.testframework.sm.runner.states.TestStateInfo; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class SelectTestStep extends BaseListPopupStep { + private final Map myRecords; + private final LocationTestRunner myRunner; + private final Project myProject; + + public SelectTestStep(Project project, Map records, LocationTestRunner runner) { + super("Debug Recent Tests", getUrls(records)); + myRunner = runner; + myProject = project; + myRecords = records; + } + + private static List getUrls(Map records) { + List list = ContainerUtil.newArrayList(records.keySet()); + Collections.sort(list, new Comparator() { + @Override + public int compare(String o1, String o2) { + String path1 = VirtualFileManager.extractPath(o1); + String path2 = VirtualFileManager.extractPath(o2); + return path1.compareTo(path2); + } + }); + return list; + } + + @NotNull + @Override + public String getTextFor(String value) { + return VirtualFileManager.extractPath(value); + } + + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public Icon getIconFor(String value) { + TestStateStorage.Record record = myRecords.get(value); + TestStateInfo.Magnitude magnitude = TestIconMapper.getMagnitude(record.magnitude); + return TestIconMapper.getIcon(magnitude); + } + + private Location getLocation(String url) { + String protocol = VirtualFileManager.extractProtocol(url); + String path = VirtualFileManager.extractPath(url); + + if (protocol != null) { + List locations = JavaTestLocator.INSTANCE.getLocation(protocol, path, myProject, GlobalSearchScope.allScope(myProject)); + if (!locations.isEmpty()) { + return locations.get(0); + } + } + + return null; + } + + @Override + public PopupStep onChosen(String url, boolean finalChoice) { + Location location = getLocation(url); + myRunner.run(location); + return null; + } +} diff --git a/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java b/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java new file mode 100644 index 000000000000..0b064d8c95a4 --- /dev/null +++ b/java/execution/impl/src/com/intellij/testIntegration/ShowRecentTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2015 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.TestStateStorage; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.ListPopupStep; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.util.Time; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.util.Date; +import java.util.Map; + +public class ShowRecentTests extends AnAction { + private static final int TEST_LIMIT = 20; + + private static Date getSinceDate() { + return new Date(System.currentTimeMillis() - 2 * Time.HOUR); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); + if (project == null) return; + + Map records = TestStateStorage.getInstance(project).getRecentTests(TEST_LIMIT, getSinceDate()); + LocationTestRunner testRunner = new LocationTestRunnerImpl(); + + SelectTestStep selectStepTest = new SelectTestStep(project, records, testRunner); + RecentTestsListPopup popup = new RecentTestsListPopup(selectStepTest, testRunner); + popup.showCenteredInCurrentWindow(project); + } +} + +class RecentTestsListPopup extends ListPopupImpl { + private final LocationTestRunner myTestRunner; + + public RecentTestsListPopup(ListPopupStep popupStep, LocationTestRunner testRunner) { + super(popupStep); + myTestRunner = testRunner; + shiftReleased(); + registerActions(this); + } + + private void registerActions(ListPopupImpl popup) { + popup.registerAction("alternate", KeyStroke.getKeyStroke("shift pressed SHIFT"), new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + shiftPressed(); + } + }); + popup.registerAction("restoreDefault", KeyStroke.getKeyStroke("released SHIFT"), new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + shiftReleased(); + } + }); + popup.registerAction("invokeAction", KeyStroke.getKeyStroke("shift ENTER"), new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + handleSelect(true); + } + }); + } + + private void shiftPressed() { + setCaption("Run Recent Tests"); + myTestRunner.setMode(LocationTestRunner.Mode.RUN); + } + + private void shiftReleased() { + setCaption("Debug Recent Tests"); + myTestRunner.setMode(LocationTestRunner.Mode.DEBUG); + } +} + + diff --git a/java/java-tests/java-tests.iml b/java/java-tests/java-tests.iml index e45989a2ad8c..8ebd2aab4a94 100644 --- a/java/java-tests/java-tests.iml +++ b/java/java-tests/java-tests.iml @@ -48,5 +48,6 @@ + \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt b/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt new file mode 100644 index 000000000000..32c716e4a98c --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/testIntergration/RecentTestsTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2015 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.testIntergration + +import com.intellij.execution.TestStateStorage +import com.intellij.openapi.project.Project +import com.intellij.testFramework.UsefulTestCase.* +import com.intellij.testIntegration.LocationTestRunner +import com.intellij.testIntegration.SelectTestStep +import org.junit.Test +import org.mockito.Mockito.mock +import java.util.* + +class RecentTestsStepTest { + val project = mock(Project::class.java) + val runner = mock(LocationTestRunner::class.java) + + @Test + fun `suites comes before their children tests`() { + val map: MutableMap = hashMapOf() + val now = Date() + map.put("java:suite://JavaFormatterSuperDuperTest", TestStateStorage.Record(1, now)) + map.put("java:test://Test.textXXX", TestStateStorage.Record(1, now)) + map.put("java:suite://Test", TestStateStorage.Record(1, now)) + map.put("java:test://Test.textYYY", TestStateStorage.Record(1, now)) + map.put("java:test://Test.textZZZ", TestStateStorage.Record(1, now)) + map.put("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", TestStateStorage.Record(1, now)) + map.put("java:test://Test.textQQQ", TestStateStorage.Record(1, now)) + map.put("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", TestStateStorage.Record(1, now)) + + val step = SelectTestStep(project, map, runner) + + val expected = listOf( + "java:suite://JavaFormatterSuperDuperTest", + "java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", + "java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", + "java:suite://Test", + "java:test://Test.textQQQ", + "java:test://Test.textXXX", + "java:test://Test.textYYY", + "java:test://Test.textZZZ" + ) + assertContainsOrdered(step.values, expected) + assertSize(8, step.values) + } + + + @Test + fun `shown value without protocol`() { + val step = SelectTestStep(project, emptyMap(), runner) + var shownValue = step.getTextFor("java:suite://JavaFormatterSuperDuperTest") + assertEquals(shownValue, "JavaFormatterSuperDuperTest") + + shownValue = step.getTextFor("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt") + assertEquals(shownValue, "JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt") + } + +} \ 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 4a7fa538a669..770f8203925c 100644 --- a/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java +++ b/platform/lang-impl/src/com/intellij/execution/TestStateStorage.java @@ -24,6 +24,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.EnumeratorStringDescriptor; import com.intellij.util.io.IOUtil; @@ -35,7 +36,9 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.Date; +import java.util.Map; import java.util.concurrent.ScheduledFuture; /** @@ -129,6 +132,29 @@ public class TestStateStorage implements Disposable { } } + @Nullable + public Map getRecentTests(int limit, Date since) { + if (myMap == null) return null; + + Map result = ContainerUtil.newHashMap(); + try { + for (String key : myMap.getAllKeysWithExistingMapping()) { + Record record = myMap.get(key); + if (record != null && record.date.compareTo(since) > 0) { + result.put(key, record); + if (result.size() >= limit) { + break; + } + } + } + } + catch (IOException e) { + thingsWentWrongLetsReinitialize(e, "Can't get recent tests"); + } + + return result; + } + public synchronized void writeState(@NotNull String testUrl, Record record) { if (myMap == null) return; try { diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index e22c82b66544..d80c3826c6c7 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -554,6 +554,7 @@ action.OptimizeImports.description=Remove unused imports and reorder/reorganize action.RearrangeCode.text=Rearrange Code action.RearrangeCode.description=Rearrange code action.ShowReformatFileDialog.text=Show Reformat File Dialog +action.ShowRecentTests.text=Recent Tests action.MoveStatementDown.text=Move Statement Do_wn action.MoveStatementDown.description=Move selected statements one line down action.MoveStatementUp.text=Move Statement _Up diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index a9a2a7c15324..b9150686fcbd 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1707,6 +1707,7 @@ +