Recent tests collapses tests executed from the same run configuration.

- provided simple versioning of TestStateStorage.Record
- run configuration name hash is saved into TestStateStorage.Record
- lots of refactorings
This commit is contained in:
Yaroslav Lepenkin
2016-05-20 17:25:37 +03:00
parent b32e6aa73d
commit b6f14644b4
11 changed files with 531 additions and 683 deletions
@@ -136,6 +136,8 @@ public abstract class JavaTestFrameworkRunnableState<T extends
Disposer.register(getConfiguration().getProject(), consoleView);
final OSProcessHandler handler = createHandler(executor);
handler.putUserData(TestStateStorage.RUN_CONFIGURATION_NAME_KEY, getConfiguration().getName());
consoleView.attachToProcess(handler);
final AbstractTestProxy root = viewer.getRoot();
if (root instanceof TestProxyRoot) {
@@ -15,12 +15,19 @@
*/
package com.intellij.testIntegration;
import com.intellij.execution.Executor;
import com.intellij.execution.Location;
import com.intellij.execution.ProgramRunnerUtil;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.executors.DefaultRunExecutor;
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 com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface RecentTestRunner {
@@ -30,12 +37,16 @@ public interface RecentTestRunner {
}
void setMode(Mode mode);
void run(Location location);
void run(String url);
void run(RunnerAndConfigurationSettings configuration);
}
class RecentTestRunnerImpl implements RecentTestRunner {
private static AnAction RUN = ActionManager.getInstance().getAction("RunClass");
private static AnAction DEBUG = ActionManager.getInstance().getAction("DebugClass");
private final Project myProject;
private final TestLocator myTestLocator;
protected AnAction myCurrentAction = RUN;
@@ -50,7 +61,25 @@ class RecentTestRunnerImpl implements RecentTestRunner {
}
}
public void run(final Location location) {
public RecentTestRunnerImpl(Project project, TestLocator testLocator) {
myProject = project;
myTestLocator = testLocator;
}
@Override
public void run(RunnerAndConfigurationSettings configuration) {
Executor executor = myCurrentAction == RUN ? DefaultRunExecutor.getRunExecutorInstance()
: DefaultDebugExecutor.getDebugExecutorInstance();
ProgramRunnerUtil.executeConfiguration(myProject, configuration, executor);
}
public void run(@NotNull String url) {
Location location = myTestLocator.getLocation(url);
if (location == null) {
return;
}
DataContext data = new DataContext() {
@Nullable
@Override
@@ -0,0 +1,106 @@
/*
* 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.RunnerAndConfigurationSettings
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.util.containers.ContainerUtil
import java.util.*
class RecentTestsData {
private val suitePacks = hashMapOf<String, SuitePackInfo>()
private var testsWithoutSuites: MutableList<TestInfo> = ContainerUtil.newArrayList<TestInfo>()
fun addSuite(url: String,
magnitude: TestStateInfo.Magnitude,
runDate: Date,
runConfiguration: RunnerAndConfigurationSettings)
{
val suiteInfo = SuiteInfo(url, magnitude, runDate, runConfiguration)
val suitePack = suitePacks[runConfiguration.uniqueID]
if (suitePack != null) {
suitePack.addSuite(suiteInfo)
return
}
suitePacks[runConfiguration.uniqueID] = SuitePackInfo(runConfiguration, suiteInfo)
}
fun addTest(url: String,
magnitude: TestStateInfo.Magnitude,
runDate: Date,
runConfiguration: RunnerAndConfigurationSettings) {
val testInfo = TestInfo(url, magnitude, runDate, runConfiguration)
val suite = findSuite(url, runConfiguration)
if (suite != null) {
suite.addTest(testInfo)
return
}
testsWithoutSuites.add(testInfo)
}
private fun findSuite(url: String, runConfiguration: RunnerAndConfigurationSettings): SuiteInfo? {
val pack: SuitePackInfo = suitePacks[runConfiguration.uniqueID] ?: return null
val testName = VirtualFileManager.extractPath(url)
pack.suites.forEach {
if (testName.startsWith(it.suiteName)) {
return it
}
}
return null
}
fun getTestsToShow(): List<RecentTestsPopupEntry> {
testsWithoutSuites.forEach {
val url = it.url
findSuite(url, it.runConfiguration)?.addTest(it)
}
val packsByDate = suitePacks.values.sortedByDescending { it.runDate }
return packsByDate.fold(listOf(), { list, pack -> list + pack.entriesToShow() })
}
}
fun SuitePackInfo.entriesToShow(): List<RecentTestsPopupEntry> {
if (suites.size == 1) {
return suites[0].entriesToShow()
}
val failedSuites = suites.filter { it.failedTests.size > 0 }
if (failedSuites.size == 0) {
return listOf(this)
}
return failedSuites + this
}
fun SuiteInfo.entriesToShow(): List<RecentTestsPopupEntry> {
val failed = failedTests
if (failed.size > 0) {
return failed.sortedByDescending { it.runDate } + this
}
return listOf(this)
}
@@ -15,42 +15,99 @@
*/
package com.intellij.testIntegration;
import com.intellij.execution.RunManagerEx;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.TestStateStorage;
import com.intellij.execution.configurations.ConfigurationType;
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.Function;
import com.intellij.openapi.project.Project;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.List;
import java.util.Map;
import static com.intellij.execution.testframework.sm.runner.states.TestStateInfo.Magnitude.*;
import static com.intellij.testIntegration.TestInfo.select;
import static com.intellij.execution.testframework.sm.runner.states.TestStateInfo.Magnitude.values;
interface ConfigurationByRecordProvider {
RunnerAndConfigurationSettings getConfiguration(TestStateStorage.Record record);
}
class RunConfigurationByRecordProvider implements ConfigurationByRecordProvider {
private final Project myProject;
private final Map<Integer, RunnerAndConfigurationSettings> myConfigurationsMap = ContainerUtil.newHashMap();
public RunConfigurationByRecordProvider(Project project) {
myProject = project;
initRunConfigurationsMap();
}
@Override
public RunnerAndConfigurationSettings getConfiguration(TestStateStorage.Record record) {
Integer runConfigurationHash = new Integer((int)record.configurationHash);
return myConfigurationsMap.get(runConfigurationHash);
}
private void initRunConfigurationsMap() {
RunManagerEx manager = RunManagerEx.getInstanceEx(myProject);
ConfigurationType[] types = manager.getConfigurationFactories();
for (ConfigurationType type : types) {
Map<String, List<RunnerAndConfigurationSettings>> structure = manager.getStructure(type);
for (Map.Entry<String, List<RunnerAndConfigurationSettings>> e : structure.entrySet()) {
for (RunnerAndConfigurationSettings settings : e.getValue()) {
myConfigurationsMap.put(settings.getName().hashCode(), settings);
}
}
}
}
}
public class RecentTestsListProvider {
private final Map<String, TestStateStorage.Record> myRecords;
private final ConfigurationByRecordProvider myConfigurationProvider;
public RecentTestsListProvider(Map<String, TestStateStorage.Record> records) {
myRecords = records;
public RecentTestsListProvider(ConfigurationByRecordProvider configurationProvider, Map<String, TestStateStorage.Record> records) {
myRecords = records;
myConfigurationProvider = configurationProvider;
}
public List<String> getUrlsToShowFromHistory() {
public List<RecentTestsPopupEntry> getTestsToShow() {
if (myRecords == null) return ContainerUtil.emptyList();
RecentTestsData data = new RecentTestsData();
for (Map.Entry<String, TestStateStorage.Record> entry : myRecords.entrySet()) {
String url = entry.getKey();
TestStateStorage.Record record = entry.getValue();
if (TestLocator.canLocate(url)) {
data.addTest(url, getMagnitude(record.magnitude), record.date);
handleUrl(data, url, record);
}
}
return data.getSortedTestsList();
return data.getTestsToShow();
}
private void handleUrl(RecentTestsData data, String url, TestStateStorage.Record record) {
TestStateInfo.Magnitude magnitude = getMagnitude(record.magnitude);
if (magnitude == null) {
return;
}
RunnerAndConfigurationSettings runConfiguration = myConfigurationProvider.getConfiguration(record);
if (TestLocator.isSuite(url)) {
if (runConfiguration != null) {
data.addSuite(url, magnitude, record.date, runConfiguration);
}
}
else {
data.addTest(url, magnitude, record.date, runConfiguration);
}
}
private static TestStateInfo.Magnitude getMagnitude(int magnitude) {
for (TestStateInfo.Magnitude m : values()) {
if (m.getValue() == magnitude) {
@@ -59,244 +116,4 @@ public class RecentTestsListProvider {
}
return null;
}
}
class RecentTestsData {
private static Comparator<TestInfo> BY_PATH_COMPARATOR = (o1, o2) -> {
String path1 = VirtualFileManager.extractPath(o1.getUrl());
String path2 = VirtualFileManager.extractPath(o2.getUrl());
return path1.compareTo(path2);
};
private static Comparator<SuiteInfo> SUITE_BY_RECENT_COMPARATOR =
(o1, o2) -> -o1.getMostRecentRunDate().compareTo(o2.getMostRecentRunDate());
private static Comparator<TestInfo> TEST_BY_RECENT_COMPARATOR = (o1, o2) -> -o1.getRunDate().compareTo(o2.getRunDate());
private final Map<String, SuiteInfo> mySuites = ContainerUtil.newHashMap();
private List<TestInfo> myTestsWithoutSuites = ContainerUtil.newArrayList();
public void addTest(String url, TestStateInfo.Magnitude magnitude, Date runDate) {
if (TestLocator.isSuite(url)) {
mySuites.put(url, new SuiteInfo(url, magnitude, runDate));
return;
}
TestInfo testInfo = new TestInfo(url, magnitude, runDate);
SuiteInfo suite = getSuite(url);
if (suite != null) {
suite.addTest(testInfo);
return;
}
myTestsWithoutSuites.add(testInfo);
}
@Nullable
private SuiteInfo getSuite(String url) {
String testName = VirtualFileManager.extractPath(url);
for (SuiteInfo info : mySuites.values()) {
String suiteName = info.getSuiteName();
if (testName.startsWith(suiteName)) {
return info;
}
}
return null;
}
public List<String> getSortedTestsList() {
distributeUnmatchedTests();
List<String> result = ContainerUtil.newArrayList();
fillWithTests(result, ERROR_INDEX, FAILED_INDEX);
fillWithTests(result, COMPLETE_INDEX, PASSED_INDEX, IGNORED_INDEX);
return result;
}
private void fillWithTests(List<String> result, TestStateInfo.Magnitude... magnitudes) {
List<SuiteInfo> suites = ContainerUtil.newArrayList(mySuites.values());
List<SuiteInfo> failedSuites = select(suites, magnitudes);
List<TestInfo> failedTests = select(myTestsWithoutSuites, magnitudes);
sortByPath(failedSuites);
sortByPath(failedTests);
sortSuitesByRecent(failedSuites);
sortTestsByRecent(failedTests);
fillWithSuites(result, failedSuites);
fillWithTests(result, failedTests);
}
private static void sortSuitesByRecent(List<SuiteInfo> suites) {
Collections.sort(suites, SUITE_BY_RECENT_COMPARATOR);
}
private static void sortTestsByRecent(List<TestInfo> tests) {
Collections.sort(tests, TEST_BY_RECENT_COMPARATOR);
}
private static void sortByPath(List<? extends TestInfo> list) {
Collections.sort(list, BY_PATH_COMPARATOR);
}
private static void fillWithTests(List<String> result, List<TestInfo> tests) {
for (TestInfo info : tests) {
result.add(info.getUrl());
}
}
private static void fillWithSuites(List<String> result, List<SuiteInfo> suites) {
for (SuiteInfo suite : suites) {
result.addAll(suiteToTestList(suite));
}
}
private static List<String> suiteToTestList(SuiteInfo suite) {
List<String> result = ContainerUtil.newArrayList();
if (suite.canTrustSuiteMagnitude() && suite.isPassed()) {
result.add(suite.getUrl());
return result;
}
List<TestInfo> failedTests = suite.getFailedTests();
sortTestsByRecent(failedTests);
if (failedTests.size() == suite.getTotalTestsCount()) {
result.add(suite.getUrl());
}
else if (failedTests.size() < 3) {
result.addAll(ContainerUtil.map(failedTests, testInfo -> {
return testInfo.getUrl();
}));
result.add(suite.getUrl());
}
else {
result.add(suite.getUrl());
result.addAll(ContainerUtil.map(failedTests, testInfo -> {
return testInfo.getUrl();
}));
}
return result;
}
private void distributeUnmatchedTests() {
List<TestInfo> noSuites = ContainerUtil.newSmartList();
for (TestInfo test : myTestsWithoutSuites) {
String url = test.getUrl();
SuiteInfo suite = getSuite(url);
if (suite != null) {
suite.addTest(test);
}
else {
noSuites.add(test);
}
}
myTestsWithoutSuites = noSuites;
}
}
class SuiteInfo extends TestInfo {
private final String mySuiteName;
private Set<TestInfo> tests = ContainerUtil.newHashSet();
public SuiteInfo(String url, TestStateInfo.Magnitude magnitude, Date runDate) {
super(url, magnitude, runDate);
mySuiteName = VirtualFileManager.extractPath(url);
}
public Date getMostRecentRunDate() {
Date mostRecent = getRunDate();
for (TestInfo test : tests) {
Date testDate = test.getRunDate();
if (testDate.compareTo(mostRecent) > 0) {
mostRecent = testDate;
}
}
return mostRecent;
}
public boolean canTrustSuiteMagnitude() {
Date suiteRunDate = getRunDate();
for (TestInfo test : tests) {
if (test.getRunDate().getTime() > suiteRunDate.getTime()) {
return false;
}
}
return true;
}
public boolean isPassed() {
return getMagnitude() == IGNORED_INDEX || getMagnitude() == PASSED_INDEX || getMagnitude() == COMPLETE_INDEX;
}
public List<TestInfo> getFailedTests() {
List<TestInfo> failed = ContainerUtil.newSmartList();
for (TestInfo test : tests) {
if (test.getMagnitude() == FAILED_INDEX || test.getMagnitude() == ERROR_INDEX) {
failed.add(test);
}
}
return failed;
}
public String getSuiteName() {
return mySuiteName;
}
public void addTest(TestInfo info) {
tests.add(info);
}
public int getTotalTestsCount() {
return tests.size();
}
}
class TestInfo {
private final Date runDate;
private final String url;
private final TestStateInfo.Magnitude magnitude;
public TestInfo(String url, TestStateInfo.Magnitude magnitude, Date runDate) {
this.url = url;
this.magnitude = magnitude;
this.runDate = runDate;
}
public Date getRunDate() {
return runDate;
}
public String getUrl() {
return url;
}
public TestStateInfo.Magnitude getMagnitude() {
return magnitude;
}
public static <T extends TestInfo> List<T> select(Collection<T> infos, final TestStateInfo.Magnitude... magnitudes) {
return ContainerUtil.filter(infos, new Condition<T>() {
@Override
public boolean value(T t) {
for (TestStateInfo.Magnitude magnitude : magnitudes) {
if (t.getMagnitude() == magnitude) {
return true;
}
}
return false;
}
});
}
}
}
@@ -1,62 +0,0 @@
/*
* 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.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.vfs.VirtualFileManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.List;
import java.util.Map;
public class SelectTestStep extends BaseListPopupStep<String> {
private final RecentTestRunner myRunner;
private final TestLocator myTestLocator;
private final Map<String, Icon> myIcons;
public SelectTestStep(List<String> urls, Map<String, Icon> icons, RecentTestRunner runner, TestLocator locator) {
super("Debug Recent Tests", urls);
myRunner = runner;
myIcons = icons;
myTestLocator = locator;
}
@Override
public Icon getIconFor(String value) {
return myIcons.get(value);
}
@NotNull
@Override
public String getTextFor(String value) {
return VirtualFileManager.extractPath(value);
}
@Override
public boolean isSpeedSearchEnabled() {
return true;
}
@Override
public PopupStep onChosen(String url, boolean finalChoice) {
Location location = myTestLocator.getLocation(url);
myRunner.run(location);
return null;
}
}
@@ -0,0 +1,101 @@
/*
* 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.testframework.TestIconMapper
import com.intellij.openapi.keymap.MacKeymapUtil
import com.intellij.openapi.ui.popup.ListPopupStep
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.popup.list.ListPopupImpl
import com.intellij.util.PsiNavigateUtil
import java.awt.event.ActionEvent
import java.awt.event.KeyEvent
import javax.swing.AbstractAction
import javax.swing.Icon
import javax.swing.KeyStroke
class RecentTestsListPopup(popupStep: ListPopupStep<RecentTestsPopupEntry>,
private val testRunner: RecentTestRunner,
private val locator: TestLocator)
: ListPopupImpl(popupStep)
{
init {
shiftReleased()
registerActions(this)
val shift = if (SystemInfo.isMac) MacKeymapUtil.SHIFT else "Shift"
setAdText("Debug with $shift, navigate with F4")
}
private fun registerActions(popup: ListPopupImpl) {
popup.registerAction("alternate", KeyStroke.getKeyStroke("shift pressed SHIFT"), object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) = shiftPressed()
})
popup.registerAction("restoreDefault", KeyStroke.getKeyStroke("released SHIFT"), object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) = shiftReleased()
})
popup.registerAction("invokeAction", KeyStroke.getKeyStroke("shift ENTER"), object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) = handleSelect(true)
})
popup.registerAction("navigate", KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
val values = selectedValues
if (values.size == 1) {
val element = (values[0] as RecentTestsPopupEntry).navigatableElement(locator)
if (element != null) {
cancel()
PsiNavigateUtil.navigate(element)
}
}
}
})
}
private fun shiftPressed() {
setCaption("Debug Recent Tests")
testRunner.setMode(RecentTestRunner.Mode.DEBUG)
}
private fun shiftReleased() {
setCaption("Run Recent Tests")
testRunner.setMode(RecentTestRunner.Mode.RUN)
}
}
class SelectTestStep(tests: List<RecentTestsPopupEntry>,
private val runner: RecentTestRunner)
: BaseListPopupStep<RecentTestsPopupEntry>("Debug Recent Tests", tests)
{
override fun getIconFor(value: RecentTestsPopupEntry): Icon? {
return TestIconMapper.getIcon(value.magnitude)
}
override fun getTextFor(value: RecentTestsPopupEntry) = value.presentation
override fun isSpeedSearchEnabled() = true
override fun onChosen(entry: RecentTestsPopupEntry, finalChoice: Boolean): PopupStep<RecentTestsPopupEntry>? {
entry.run(runner)
return null
}
}
@@ -15,28 +15,15 @@
*/
package com.intellij.testIntegration;
import com.intellij.execution.Location;
import com.intellij.execution.TestStateStorage;
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;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.util.Function;
import com.intellij.util.PsiNavigateUtil;
import com.intellij.util.Time;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -60,88 +47,29 @@ public class ShowRecentTests extends AnAction {
final TestStateStorage testStorage = TestStateStorage.getInstance(project);
final TestLocator testLocator = new TestLocator(project);
final RecentTestRunnerImpl testRunner = new RecentTestRunnerImpl();
final RecentTestRunnerImpl testRunner = new RecentTestRunnerImpl(project, testLocator);
final Map<String, TestStateStorage.Record> records = testStorage.getRecentTests(TEST_LIMIT, getSinceDate());
RecentTestsListProvider listProvider = new RecentTestsListProvider(records);
List<String> urls = listProvider.getUrlsToShowFromHistory();
Map<String, Icon> icons = ContainerUtil.map2Map(urls, url -> {
return Pair.create(url, getIconFor(url, records));
});
SelectTestStep selectStepTest = new SelectTestStep(urls, icons, testRunner, testLocator);
RunConfigurationByRecordProvider configurationProvider = new RunConfigurationByRecordProvider(project);
RecentTestsListProvider listProvider = new RecentTestsListProvider(configurationProvider, records);
List<RecentTestsPopupEntry> entries = listProvider.getTestsToShow();
SelectTestStep selectStepTest = new SelectTestStep(entries, testRunner);
RecentTestsListPopup popup = new RecentTestsListPopup(selectStepTest, testRunner, testLocator);
popup.showCenteredInCurrentWindow(project);
cleanDeadTests(entries, testLocator, testStorage);
}
private static void cleanDeadTests(List<RecentTestsPopupEntry> entries, TestLocator testLocator, TestStateStorage testStorage) {
List<String> urls = ContainerUtil.newArrayList();
entries.forEach((entry) -> urls.addAll(entry.getTestsUrls()));
ApplicationManager.getApplication().executeOnPooledThread(new DeadTestsCleaner(testStorage, urls, testLocator));
}
private static Icon getIconFor(String value, Map<String, TestStateStorage.Record> records) {
TestStateStorage.Record record = records.get(value);
TestStateInfo.Magnitude magnitude = TestIconMapper.getMagnitude(record.magnitude);
return TestIconMapper.getIcon(magnitude);
}
}
class RecentTestsListPopup extends ListPopupImpl {
private final RecentTestRunner myTestRunner;
private final TestLocator myLocator;
public RecentTestsListPopup(ListPopupStep<String> popupStep, RecentTestRunner testRunner, TestLocator locator) {
super(popupStep);
myTestRunner = testRunner;
myLocator = locator;
shiftReleased();
registerActions(this);
String shift = SystemInfo.isMac ? MacKeymapUtil.SHIFT : "Shift";
setAdText("Debug with " + shift + ", navigate with F4");
}
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);
}
});
popup.registerAction("navigate", KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Object[] values = getSelectedValues();
if (values.length == 1) {
Location location = myLocator.getLocation(values[0].toString());
if (location != null) {
cancel();
PsiNavigateUtil.navigate(location.getPsiElement());
}
}
}
});
}
private void shiftPressed() {
setCaption("Debug Recent Tests");
myTestRunner.setMode(RecentTestRunner.Mode.DEBUG);
}
private void shiftReleased() {
setCaption("Run Recent Tests");
myTestRunner.setMode(RecentTestRunner.Mode.RUN);
}
}
@@ -0,0 +1,98 @@
/*
* 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.RunnerAndConfigurationSettings
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo.Magnitude.*
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
import java.util.*
interface RecentTestsPopupEntry {
val runDate: Date
val magnitude: TestStateInfo.Magnitude
val presentation: String
val testsUrls: List<String>
fun run(runner: RecentTestRunner)
open fun navigatableElement(locator: TestLocator): PsiElement? = null
}
open class TestInfo(val url: String,
override val magnitude: TestStateInfo.Magnitude,
override val runDate: Date,
val runConfiguration: RunnerAndConfigurationSettings) : RecentTestsPopupEntry
{
override val presentation = VirtualFileManager.extractPath(url)
override val testsUrls = listOf(url)
override fun run(runner: RecentTestRunner) {
runner.run(url)
}
override fun navigatableElement(locator: TestLocator) = locator.getLocation(url)?.psiElement
}
class SuiteInfo(url: String, magnitude: TestStateInfo.Magnitude, runDate: Date, runConfiguration: RunnerAndConfigurationSettings)
: TestInfo(url, magnitude, runDate, runConfiguration)
{
private val tests = hashSetOf<TestInfo>()
override val testsUrls: List<String>
get() = tests.fold(listOf<String>(), { acc, testEntry -> acc + testEntry.testsUrls })
val suiteName = VirtualFileManager.extractPath(url)
val failedTests: List<TestInfo>
get() = tests.filter { it.magnitude == FAILED_INDEX || it.magnitude == ERROR_INDEX }
fun addTest(info: TestInfo) = tests.add(info)
override val presentation = suiteName
}
class SuitePackInfo(val runSettings: RunnerAndConfigurationSettings, initial: SuiteInfo) : RecentTestsPopupEntry {
val suites = ContainerUtil.newArrayList<SuiteInfo>()
init {
addSuite(initial)
}
fun addSuite(s: SuiteInfo) = suites.add(s)
override val runDate = suites.map { it.runDate }.min()!!
override val magnitude = COMPLETE_INDEX
override val presentation = runSettings.name
override val testsUrls: List<String>
get() = suites.fold(listOf<String>(), { list, suite -> list + suite.testsUrls })
override fun run(runner: RecentTestRunner) {
runner.run(runSettings)
}
}
@@ -15,290 +15,73 @@
*/
package com.intellij.testIntergration
import com.intellij.execution.Location
import com.intellij.execution.TestStateStorage
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.testFramework.LightIdeaTestCase
import com.intellij.testIntegration.*
import com.intellij.testIntegration.RecentTestsData
import org.assertj.core.api.Assertions.assertThat
import org.mockito.Matchers
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import java.util.*
fun passed(date: Date) = TestStateStorage.Record(TestStateInfo.Magnitude.PASSED_INDEX.value, date)
fun failed(date: Date) = TestStateStorage.Record(TestStateInfo.Magnitude.FAILED_INDEX.value, date)
class RecentTestsStepTest: LightIdeaTestCase() {
val runner = mock(RecentTestRunner::class.java)
val passed = passed(Date(0))
val failed = failed(Date(0))
class TestStorage {
private val map: MutableMap<String, TestStateStorage.Record> = hashMapOf()
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, language)
}
fun addSuite(name: String, magnitude: TestStateInfo.Magnitude, date: Date = Date(0), language: String = "java") {
val record = TestStateStorage.Record(magnitude.value, date)
map.put("$language:suite://$name", record)
}
fun addTest(name: String, magnitude: TestStateInfo.Magnitude, date: Date = Date(0)) {
val record = TestStateStorage.Record(magnitude.value, date)
map.put("java:test://$name", record)
}
lateinit var data: RecentTestsData
lateinit var allTests: RunnerAndConfigurationSettings
lateinit var now: Date
fun addTest(name: String, pass: Boolean, date: Date = Date(0)) {
val magnitude = if (pass) TestStateInfo.Magnitude.PASSED_INDEX else TestStateInfo.Magnitude.FAILED_INDEX
addTest(name, magnitude, date)
}
fun getMap() = map
override fun setUp() {
super.setUp()
data = RecentTestsData()
allTests = mock(RunnerAndConfigurationSettings::class.java)
`when`(allTests.uniqueID).thenAnswer { "JUnit.all tests" }
`when`(allTests.name).thenAnswer { "all tests" }
now = Date()
}
fun getSuite(name: String, language: String = "java") = map["$language:suite://$name"]
fun `test all tests passed`() {
data.addTest("java:test://Test.textXXX", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addSuite("java:suite://Test", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addSuite("java:suite://JavaFormatterSuperDuperTest", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://Test.textYYY", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://Test.textZZZ", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://Test.textQQQ", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
val tests = data.getTestsToShow()
assertThat(tests).hasSize(1)
assertThat(tests[0].presentation).isEqualTo("all tests")
}
fun `test if one failed in run configuration show failed suite`() {
data.addSuite("java:suite://JavaFormatterSuperDuperTest", TestStateInfo.Magnitude.FAILED_INDEX, now, allTests)
data.addSuite("java:suite://Test", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", TestStateInfo.Magnitude.FAILED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
fun removeUrl(url: String) {
map.remove(url)
}
data.addTest("java:test://Test.textXXX", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
val tests = data.getTestsToShow()
assertThat(tests).hasSize(2)
assertThat(tests[0].presentation).isEqualTo("JavaFormatterSuperDuperTest")
assertThat(tests[1].presentation).isEqualTo("all tests")
}
fun `test show sorted by date`() {
val storage = TestStorage()
fun `test if configuration with single test show failed test`() {
data.addSuite("java:suite://JavaFormatterSuperDuperTest", TestStateInfo.Magnitude.FAILED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", TestStateInfo.Magnitude.FAILED_INDEX, now, allTests)
data.addTest("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", TestStateInfo.Magnitude.PASSED_INDEX, now, allTests)
storage.addSuite("ASTest", true, Date(1000))
storage.addSuite("JSTest", true, Date(1200))
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf("JSTest", "ASTest"))
val tests = data.getTestsToShow()
assertThat(tests).hasSize(2)
assertThat(tests[0].presentation).isEqualTo("JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt")
assertThat(tests[1].presentation).isEqualTo("JavaFormatterSuperDuperTest")
}
fun `test show tests sorted by date`() {
val storage = TestStorage()
storage.addSuite("ASTest", false, Date(0))
storage.addTest("ASTest.xxxx", true, Date(99999))
storage.addTest("ASTest.aaaa", false, Date(10000))
storage.addTest("ASTest.cccc", false, Date(20000))
storage.addTest("ASTest.bbbb", false, Date(30000))
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf("ASTest", "ASTest.bbbb", "ASTest.cccc", "ASTest.aaaa"))
}
fun `test show ignored`() {
val storage = TestStorage()
storage.addSuite("ASTest", TestStateInfo.Magnitude.IGNORED_INDEX)
storage.addTest("ASTest.ignored", TestStateInfo.Magnitude.IGNORED_INDEX)
storage.addTest("ASTest.passed", pass = true)
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf("ASTest"))
}
fun `test when suite passed - show only suite`() {
val map: MutableMap<String, TestStateStorage.Record> = hashMapOf()
map.put("java:suite://JavaFormatterSuperDuperTest", passed)
map.put("java:test://Test.textXXX", passed)
map.put("java:suite://Test", passed)
map.put("java:test://Test.textYYY", passed)
map.put("java:test://Test.textZZZ", passed)
map.put("java:test://JavaFormatterSuperDuperTest.testItMakesMeSadToFixIt", passed)
map.put("java:test://Test.textQQQ", passed)
map.put("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", passed)
val expected = listOf(
"java:suite://JavaFormatterSuperDuperTest",
"java:suite://Test"
)
val list = getSortedList(map)
assertThat(list).isEqualTo(expected)
}
private fun getSortedList(map: MutableMap<String, TestStateStorage.Record>): List<String> {
val provider = RecentTestsListProvider(map)
return provider.urlsToShowFromHistory
}
fun `test show only java tests`() {
val storage = TestStorage()
storage.addSuite("JavaTest1", true)
storage.addSuite("JavaTest2", 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"))
}
fun `test show failed first`() {
val map: MutableMap<String, TestStateStorage.Record> = hashMapOf()
map.put("java:suite://JavaFormatterSuperDuperTest", failed)
map.put("java:test://Test.textXXX", passed)
map.put("java:suite://Test", passed)
map.put("java:suite://JavaFormatterFailed", failed)
map.put("java:test://JavaFormatterFailed.fail", failed)
map.put("java:test://JavaFormatterFailed.notFail", passed)
map.put("java:test://Test.textYYY", passed)
map.put("java:test://Test.textZZZ", passed)
map.put("java:test://JavaFormatterSuperDuperTest.testFail", failed)
map.put("java:test://Test.textQQQ", passed)
map.put("java:test://JavaFormatterSuperDuperTest.testUnconditionalAlignmentErrorneous", passed)
val values = getSortedList(map)
val expected = listOf(
"java:test://JavaFormatterFailed.fail",
"java:suite://JavaFormatterFailed",
"java:test://JavaFormatterSuperDuperTest.testFail",
"java:suite://JavaFormatterSuperDuperTest",
"java:suite://Test"
)
assertThat(values).isEqualTo(expected)
}
fun `test if failed more than 2 tests show suite first`() {
val storage = TestStorage()
storage.addSuite("ASTest", false)
storage.addTest("ASTest.failed1", false, Date(3000))
storage.addTest("ASTest.failed2", false, Date(2000))
storage.addTest("ASTest.failed3", false, Date(1000))
storage.addTest("ASTest.passed1", true)
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf(
"ASTest",
"ASTest.failed1",
"ASTest.failed2",
"ASTest.failed3"
))
}
fun `test if failed less than 3 tests, show tests first`() {
val storage = TestStorage()
storage.addSuite("ASTest", false)
storage.addTest("ASTest.failed1", false, Date(3000))
storage.addTest("ASTest.failed2", false, Date(2000))
storage.addTest("ASTest.passed1", true)
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf(
"ASTest.failed1",
"ASTest.failed2",
"ASTest"
))
}
fun `test if all failed show only suite`() {
val storage = TestStorage()
storage.addSuite("ASTest", false)
storage.addTest("ASTest.failed1", false)
storage.addTest("ASTest.failed2", false)
storage.addTest("ASTest.failed3", false)
storage.addTest("ASTest.failed4", false)
val sortedUrlList = getSortedList(storage.getMap())
val values = sortedUrlList.map { VirtualFileManager.extractPath(it) }
assertThat(values).isEqualTo(listOf("ASTest"))
}
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(substring)) null else mock(Location::class.java)
}
return locator
}
fun `test shown value without protocol`() {
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("<default package>", false)
storage.addSuite("<default package>", 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("<default package>")).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
}
}
@@ -20,7 +20,9 @@ import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
import com.intellij.util.containers.ContainerUtil;
@@ -44,7 +46,12 @@ import java.util.concurrent.ScheduledFuture;
*/
public class TestStateStorage implements Disposable {
public static Key<String> RUN_CONFIGURATION_NAME_KEY = Key.create("run.configuration.name");
private static final File TEST_HISTORY_PATH = new File(PathManager.getSystemPath(), "testHistory");
private static final int CURRENT_VERSION = 1;
private final File myFile;
public static File getTestHistoryRoot(Project project) {
@@ -53,11 +60,13 @@ public class TestStateStorage implements Disposable {
public static class Record {
public final int magnitude;
public final long configurationHash;
public final Date date;
public Record(int magnitude, Date date) {
public Record(int magnitude, Date date, long configurationHash) {
this.magnitude = magnitude;
this.date = date;
this.configurationHash = configurationHash;
}
}
@@ -71,18 +80,47 @@ public class TestStateStorage implements Disposable {
}
public TestStateStorage(Project project) {
String directoryPath = getTestHistoryRoot(project).getPath();
myFile = new File(getTestHistoryRoot(project).getPath() + "/testStateMap");
myFile = new File(directoryPath + "/testStateMap");
FileUtilRt.createParentDirs(myFile);
File versionFile = new File(directoryPath + "/version");
dropMapFileIfOutdated(versionFile);
try {
myMap = initializeMap();
} catch (IOException e) {
LOG.error(e);
}
myMapFlusher = FlushingDaemon.everyFiveSeconds(() -> flushMap());
myMapFlusher = FlushingDaemon.everyFiveSeconds(this::flushMap);
}
private void dropMapFileIfOutdated(File versionFile) {
if (myFile.exists() && myFile.length() > 0
&& readVersion(versionFile) != CURRENT_VERSION) {
myFile.delete();
}
try {
FileUtil.writeToFile(versionFile, Integer.toString(CURRENT_VERSION));
}
catch (IOException e) {
LOG.debug(e);
}
}
private static int readVersion(File versionFile) {
if (!versionFile.exists()) return 0;
try {
return Integer.parseInt(FileUtil.loadFile(versionFile));
}
catch (NumberFormatException | IOException e) {
return 0;
}
}
private PersistentHashMap<String, Record> initializeMap() throws IOException {
return IOUtil.openCleanOrResetBroken(getComputable(myFile), myFile);
}
@@ -97,16 +135,17 @@ public class TestStateStorage implements Disposable {
return new ThrowableComputable<PersistentHashMap<String, Record>, IOException>() {
@Override
public PersistentHashMap<String, Record> compute() throws IOException {
return new PersistentHashMap<String, Record>(file, EnumeratorStringDescriptor.INSTANCE, new DataExternalizer<Record>() {
return new PersistentHashMap<>(file, EnumeratorStringDescriptor.INSTANCE, new DataExternalizer<Record>() {
@Override
public void save(@NotNull DataOutput out, Record value) throws IOException {
out.writeInt(value.magnitude);
out.writeLong(value.date.getTime());
out.writeLong(value.configurationHash);
}
@Override
public Record read(@NotNull DataInput in) throws IOException {
return new Record(in.readInt(), new Date(in.readLong()));
return new Record(in.readInt(), new Date(in.readLong()), in.readLong());
}
});
}
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.execution.TestStateStorage;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.testframework.*;
import com.intellij.execution.testframework.actions.ScrollToTestSourceAction;
import com.intellij.execution.testframework.export.TestResultsXmlFormatter;
@@ -847,7 +848,13 @@ public class SMTestRunnerResultsForm extends TestResultsPanel
for (SMTestProxy proxy : tests) {
String url = proxy instanceof SMTestProxy.SMRootTestProxy ? ((SMTestProxy.SMRootTestProxy)proxy).getRootLocation() : proxy.getLocationUrl();
if (url != null) {
storage.writeState(url, new TestStateStorage.Record(proxy.getMagnitude(), new Date()));
ProcessHandler handler = myRoot.getHandler();
String configurationName = null;
if (handler != null) {
configurationName = handler.getUserData(TestStateStorage.RUN_CONFIGURATION_NAME_KEY);
}
storage.writeState(url, new TestStateStorage.Record(proxy.getMagnitude(), new Date(),
configurationName == null ? 0 : configurationName.hashCode()));
}
}
});