/**
* Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
*/
package org.evosuite.intellij.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompileStatusNotification;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import org.evosuite.intellij.EvoParameters;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Singleton used to run EvoSuite Maven plugin on a background process
* <p/>
* Created by arcuri on 9/29/14.
*/
public class EvoSuiteExecutor {
private static EvoSuiteExecutor singleton = new EvoSuiteExecutor();
private volatile Thread thread;
private final AtomicBoolean running = new AtomicBoolean(false);
private EvoSuiteExecutor() {
}
public static EvoSuiteExecutor getInstance() {
return singleton;
}
public boolean isAlreadyRunning() {
return running.get();
}
public synchronized void stopRun(){
if(isAlreadyRunning()){
thread.interrupt();
try {
thread.join(2000);
} catch (InterruptedException e) {
}
}
}
//TODO should refactor 'final EvoParameters params' to be independent from IntelliJ
/**
* @param params
* @param suts map from Maven module folder to list of classes to tests.
* If this latter list is null/empty, then use all classes
* in that module
* @throws IllegalArgumentException
*/
public synchronized void run(final Project project, final EvoParameters params, final Map<String, Set<String>> suts, final AsyncGUINotifier notifier)
throws IllegalArgumentException, IllegalStateException {
if (suts == null || suts.isEmpty()) {
throw new IllegalArgumentException("No specified classes to test");
}
if (params == null) {
throw new IllegalArgumentException("No defined parameters");
}
//check validity of (maven) modules
for (String modulePath : suts.keySet()) {
File dir = new File(modulePath);
if (!dir.exists()) {
throw new IllegalArgumentException("Target module folder does not exist: " + modulePath);
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Target module folder is not a folder: " + modulePath);
}
if(params.usesMaven()) {
File pom = new File(dir, "pom.xml");
if (!pom.exists()) {
throw new IllegalArgumentException("Target module folder does not contain a pom.xml file: " + modulePath);
}
}
}
if (isAlreadyRunning()) {
throw new IllegalStateException("EvoSuite already running");
}
Task.Backgroundable task = new EvoTask(project,"EvoSuite",true,null,suts,notifier,params);
BackgroundableProcessIndicator progressIndicator = new EvoIndicator(task);
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, progressIndicator);
}
private void executeOnAllModules(Map<String, Set<String>> suts, Project project, AsyncGUINotifier notifier,
EvoParameters params, ProgressIndicator progressIndicator) {
thread = Thread.currentThread();
int modules = suts.keySet().size();
int total = suts.values().stream().mapToInt(Set::size).sum();
String msg = "Going to generate tests in "+modules+" module(s) for a total of "+total+ " classes";
System.out.println(msg);
notifier.printOnConsole(msg+"\n");
if(modules > 1) {
for(Map.Entry<String,Set<String>> entry : suts.entrySet()){
notifier.printOnConsole("Module "+entry.getKey()+" -> to test "+entry.getValue().size()+" class(es) \n");
}
notifier.success(msg);
}
for (String modulePath : suts.keySet()) {
if(Thread.currentThread().isInterrupted()){
return;
}
progressIndicator.checkCanceled();
final Module module = Utils.getModule(project,modulePath);
if(module == null){
notifier.failed("Failed to determine IntelliJ module for "+modulePath);
return;
} else {
if (! Utils.compileModule(project, notifier, module)){
return;
}
}
File dir = new File(modulePath);
Process p;
SpawnProcessKeepAliveCheckerIntelliJ checker = new SpawnProcessKeepAliveCheckerIntelliJ(notifier);
try {
int port = checker.startServer();
p = ProcessRunner.execute(project, notifier, params, dir, suts.get(modulePath), port);
if (p == null) {
return;
}
boolean done = false;
while (!done) {
try {
/*
this is blocking, which is fine, as we want it
to run till completion, unless manually stopped
*/
done = p.waitFor(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
p.destroy();
progressIndicator.checkCanceled();
return;
}
progressIndicator.checkCanceled();
}
} finally {
notifier.detachLastProcess();
checker.stopServer();
}
int res = p.exitValue();
if (res != 0) {
notifier.failed("EvoSuite ended abruptly");
return;
}
}
VirtualFileManager.getInstance().asyncRefresh(null);
notifier.success("EvoSuite run is completed");
}
private class EvoIndicator extends BackgroundableProcessIndicator{
public EvoIndicator(@NotNull Task.Backgroundable task) {
super(task);
}
// @Override //can't override because final
// public void cancel(){
// }
@Override
protected void delegateRunningChange(@NotNull IndicatorAction action) {
try {
Field f = com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase.class.getDeclaredField("CANCEL_ACTION");
f.setAccessible(true);
Object obj = f.get(null);
if(action.equals(obj)){
thread.interrupt();
}
} catch (Exception e) {
}
super.delegateRunningChange(action);
}
}
private class EvoTask extends Task.Backgroundable{
private final Map<String, Set<String>> suts;
private final AsyncGUINotifier notifier;
private final EvoParameters params;
public EvoTask(@Nullable Project project,
@Nls(capitalization = Nls.Capitalization.Title) @NotNull String title,
boolean canBeCancelled,
@Nullable PerformInBackgroundOption backgroundOption,
Map<String, Set<String>> suts, AsyncGUINotifier notifier, EvoParameters params) {
super(project, title, canBeCancelled, backgroundOption);
this.suts = suts;
this.notifier = notifier;
this.params = params;
}
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
running.set(true);
executeOnAllModules(suts, getProject(), notifier, params, progressIndicator);
running.set(false);
}
@Override
public void onCancel(){
notifier.printOnConsole("\n\n\nEvoSuite run has been cancelled\n");
running.set(false);
}
@Override
public void onSuccess(){
running.set(false);
}
}
}