/**
* Copyright (c) 2016 by Brainwy Software Ltda. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.debug.pyunit;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.python.pydev.core.log.Log;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.callbacks.CallbackWithListeners;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
// Class which is friends with PyUnitView
public class PyUnitViewTestsHolder {
public static int MAX_RUNS_TO_KEEP = 20;
private static PyUnitTestRun lastPinned;
private static PyUnitTestRun currentPinned;
private static PyUnitTestRun currentSelected;
public static PyUnitTestRun getLastPinned() {
return lastPinned;
}
public static PyUnitTestRun getCurrentPinned() {
return currentPinned;
}
public static void setCurrentPinned(PyUnitTestRun pin) {
if (pin != null) {
PyUnitViewTestsHolder.lastPinned = pin;
}
PyUnitViewTestsHolder.currentPinned = pin;
onPinSelected.call(pin);
}
public static void setCurrentTest(PyUnitTestRun result) {
currentSelected = result;
}
public static PyUnitTestRun getCurrentTest() {
return currentSelected;
}
public static final CallbackWithListeners<PyUnitTestRun> onPinSelected = new CallbackWithListeners<>();
/*default*/ static final Object lockServerListeners = new Object();
/*default*/ static final LinkedList<PyUnitViewServerListener> serverListeners = new LinkedList<PyUnitViewServerListener>();
/*default*/ static class DummyPyUnitServer implements IPyUnitServer {
private IPyUnitLaunch launch;
public DummyPyUnitServer(IPyUnitLaunch launch) {
this.launch = launch;
}
@Override
public void registerOnNotifyTest(IPyUnitServerListener pyUnitViewServerListener) {
}
@Override
public IPyUnitLaunch getPyUnitLaunch() {
return this.launch;
}
};
/*default*/ static Job saveDiskIndexJob = new Job("Save PyUnit test runs") {
{
setPriority(Job.BUILD);
setSystem(true);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
saveTestsRunState(false);
return Status.OK_STATUS;
}
};
public static void restoreTestsRunState() {
try {
File workspaceMetadataFile = getPyUnitTestsDir();
SortedMap<Integer, File> files = new TreeMap<>();
// Load existing data on test runs.
final int i0 = "test_run_".length();
for (File f : workspaceMetadataFile.listFiles()) {
String name = f.getName();
if (name.endsWith(".xml") && name.startsWith("test_run_")) {
String val0 = name.substring(i0, name.length() - 4);
try {
int i = Integer.parseInt(val0);
files.put(i, f);
} catch (NumberFormatException e) {
//ignore
}
}
}
// Get the content on the launches pinned.
String pinInfoContents;
try {
pinInfoContents = FileUtils
.getFileContents(new File(workspaceMetadataFile, "test_run_pin_info.txt"));
} catch (Exception e1) {
pinInfoContents = null;
}
if (pinInfoContents == null) {
pinInfoContents = "";
}
List<String> split = StringUtils.splitKeepEmpty(pinInfoContents, '|');
int currentPin = -1;
int currentRun = -1;
int lastPin = -1;
if (split.size() > 0) {
try {
currentPin = Integer.parseInt(split.get(0));
} catch (NumberFormatException e) {
//ignore
}
}
if (split.size() > 1) {
try {
lastPin = Integer.parseInt(split.get(1));
} catch (NumberFormatException e) {
//ignore
}
}
if (split.size() > 2) {
try {
currentRun = Integer.parseInt(split.get(2));
} catch (NumberFormatException e) {
//ignore
}
}
// Erase the files that we don't want to keep (but take care for keeping the pinned ones).
int i = 0;
while (files.size() > MAX_RUNS_TO_KEEP) {
// Erase the files for the runs that we don't want to keep (and restore the remaining ones).
Iterator<Entry<Integer, File>> it = files.entrySet().iterator();
Entry<Integer, File> entry = it.next();
// We need to prevent from deleting the pinned contents.
if (entry.getKey() == currentPin) {
try {
currentPinned = PyUnitTestRun.fromXML(FileUtils.getFileContents(entry.getValue()));
setSavedDiskIndex(currentPinned, workspaceMetadataFile, i, entry);
i += 1;
if (currentPin == lastPin) {
lastPinned = currentPinned;
}
if (currentRun == currentPin) {
currentSelected = currentPinned;
}
} catch (Exception e) {
Log.log(e);
}
} else if (entry.getKey() == lastPin) {
try {
lastPinned = PyUnitTestRun.fromXML(FileUtils.getFileContents(entry.getValue()));
setSavedDiskIndex(lastPinned, workspaceMetadataFile, i, entry);
if (currentRun == lastPin) {
currentSelected = lastPinned;
}
i += 1;
} catch (Exception e) {
Log.log(e);
}
} else if (entry.getKey() == currentRun) {
try {
currentSelected = PyUnitTestRun.fromXML(FileUtils.getFileContents(entry.getValue()));
setSavedDiskIndex(currentSelected, workspaceMetadataFile, i, entry);
i += 1;
} catch (Exception e) {
Log.log(e);
}
} else {
entry.getValue().delete();
}
it.remove();
}
Set<Entry<Integer, File>> entrySet = files.entrySet();
for (Entry<Integer, File> entry : entrySet) {
File file = entry.getValue();
try {
String fileContents = FileUtils.getFileContents(file);
try {
PyUnitTestRun testRunRestored = PyUnitTestRun.fromXML(fileContents);
setSavedDiskIndex(testRunRestored, workspaceMetadataFile, i, entry);
i += 1;
// If the pinned files are current files, we have to restore them too.
if (entry.getKey() == currentPin) {
currentPinned = testRunRestored;
}
if (entry.getKey() == lastPin) {
lastPinned = testRunRestored;
}
DummyPyUnitServer pyUnitServer = new DummyPyUnitServer(testRunRestored.getPyUnitLaunch());
final PyUnitViewServerListener serverListener = new PyUnitViewServerListener(pyUnitServer,
testRunRestored);
addServerListener(serverListener);
} catch (Exception e) {
Log.log("Error with contents: " + fileContents, e);
}
} catch (Exception e) {
Log.log(e);
}
}
} catch (Exception e) {
Log.log(e);
}
}
private static void setSavedDiskIndex(PyUnitTestRun testRun, File workspaceMetadataFile, int i,
Entry<Integer, File> entry) {
if (i != entry.getKey()) {
// Restart the numbering on the disk (otherwise that number would grow forever as we
// always start numbering from the last maximum number + 1).
entry.getValue().renameTo(new File(workspaceMetadataFile, "test_run_" + i + ".xml"));
}
testRun.savedDiskIndex = i;
}
/**
* Adds a server listener to the static list of available server listeners. This is needed so that we start
* to listen to it when the view is restored later on (if it's still not visible).
*/
protected static void addServerListener(PyUnitViewServerListener serverListener) {
synchronized (lockServerListeners) {
if (serverListeners.size() + 1 > MAX_RUNS_TO_KEEP) {
serverListeners.removeFirst();
}
serverListeners.add(serverListener);
}
}
public final static Object saveTestsRunStateLock = new Object();
/**
* @param forceWriteUnfinished if true, we'll write even the unfinished test runs.
*/
public static void saveTestsRunState(boolean forceWriteUnfinished) {
try {
List<PyUnitTestRun> lst;
synchronized (lockServerListeners) {
lst = new ArrayList<>(serverListeners.size() + 2);
for (PyUnitViewServerListener pyUnitViewServerListener : serverListeners) {
lst.add(pyUnitViewServerListener.getTestRun());
}
}
synchronized (saveTestsRunStateLock) {
File workspaceMetadataFile = getPyUnitTestsDir();
int i = 0;
PyUnitTestRun currPin = currentPinned;
PyUnitTestRun currRun = currentSelected;
PyUnitTestRun lastPin = lastPinned;
boolean foundCurrPin = false;
boolean foundCurrRun = false;
boolean foundLast = false;
for (PyUnitTestRun testRun : lst) {
if (testRun == currPin) {
foundCurrPin = true;
}
if (testRun == currRun) {
foundCurrRun = true;
}
if (testRun == lastPin) {
foundLast = true;
}
if (testRun.savedDiskIndex != null && testRun.savedDiskIndex >= i) {
i = testRun.savedDiskIndex + 1;
}
}
if (!foundCurrPin && currPin != null) {
lst.add(currPin);
if (currPin.savedDiskIndex != null && currPin.savedDiskIndex >= i) {
i = currPin.savedDiskIndex + 1;
}
}
if (!foundLast && lastPin != null) {
lst.add(lastPin);
if (lastPin.savedDiskIndex != null && lastPin.savedDiskIndex >= i) {
i = lastPin.savedDiskIndex + 1;
}
}
if (!foundCurrRun && currRun != null) {
lst.add(currRun);
if (currRun.savedDiskIndex != null && currRun.savedDiskIndex >= i) {
i = currRun.savedDiskIndex + 1;
}
}
for (PyUnitTestRun testRun : lst) {
// We want to write only the deltas, so, skip it if it's already saved
if (testRun.savedDiskIndex != null) {
continue;
}
if (!forceWriteUnfinished) {
if (!testRun.getFinished()) {
continue;
}
}
String xml = testRun.toXML();
File f = new File(workspaceMetadataFile,
"test_run_" + i + ".xml");
FileUtils.writeBytesToFile(xml.getBytes(StandardCharsets.UTF_8), f);
testRun.savedDiskIndex = i;
i += 1;
}
FastStringBuffer buf = new FastStringBuffer();
if (currPin != null && currPin.savedDiskIndex != null) {
buf.append(currPin.savedDiskIndex);
}
buf.append('|');
if (lastPin != null && lastPin.savedDiskIndex != null) {
buf.append(lastPin.savedDiskIndex);
}
buf.append('|');
if (currRun != null && currRun.savedDiskIndex != null) {
buf.append(currRun.savedDiskIndex);
}
FileUtils.writeBytesToFile(buf.getBytes(), new File(workspaceMetadataFile, "test_run_pin_info.txt"));
}
} catch (Exception e) {
Log.log(e);
}
}
/**
* @return the directory to save the files
*/
private static File getPyUnitTestsDir() {
File workspaceMetadataFile = PydevPlugin.getWorkspaceMetadataFile("pyunit_tests");
if (!workspaceMetadataFile.exists()) {
workspaceMetadataFile.mkdirs();
}
return workspaceMetadataFile;
}
}