/**
* Copyright (c) 2012 by JP Moresmau
* This code is made available under the terms of the Eclipse Public License,
* version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
*/
package net.sf.eclipsefp.haskell.debug.core.internal.launch;
import java.io.File;
import java.io.StringWriter;
import java.util.Map;
import java.util.Random;
import net.sf.eclipsefp.haskell.debug.core.internal.HaskellDebugCore;
import net.sf.eclipsefp.haskell.debug.core.test.HTFParser;
import net.sf.eclipsefp.haskell.debug.core.test.ITestListener;
import net.sf.eclipsefp.haskell.debug.core.test.TestListenerManager;
import net.sf.eclipsefp.haskell.debug.core.test.TestResult;
import net.sf.eclipsefp.haskell.debug.core.test.TestSuite;
import net.sf.eclipsefp.haskell.util.ProcessRunner;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IProcess;
/**
* Launch delegate for HTF tests
* @author JP Moresmau
*
*/
public class HTFLaunchDelegate extends ExecutableOrTestSuiteHaskellLaunchDelegate {
private String filename = null;
private TestResult root;
private TestSuite suite;
private ParseThread parseThread;
private String getFilename() {
if( filename == null ) {
Random r = new Random( System.currentTimeMillis() );
int n = r.nextInt();
filename = HaskellDebugCore.getDefault().getStateLocation()
.append( n + ".json" ).toOSString(); //$NON-NLS-1$
}
return filename;
}
private String getHistoryFilename() {
return HaskellDebugCore.getDefault().getStateLocation()
.append( "htf.history.json" ).toOSString(); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate#createProcess(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IPath, java.lang.String[], java.io.File)
*/
@Override
protected IProcess createProcess( final ILaunchConfiguration configuration,
final String mode, final ILaunch launch, final IPath location, String[] cmdLine,
final File workingDir ) throws CoreException {
/**
* launch the same process, but with the list parameter, to get the list of tests without running them
*/
String [] cmd2=new String[cmdLine.length];
System.arraycopy( cmdLine, 0, cmd2, 0, cmd2.length ); /** remove --split **/
cmd2[cmd2.length-1]="--list"; //$NON-NLS-1$
final String fname = getFilename();
final File file = new File( fname );
final IProject p=getProject( configuration );
try (StringWriter sw=new StringWriter()) {
// older versions of HTF do not like the new --history flags
int code=new ProcessRunner().executeBlocking( workingDir, sw, null, cmd2 );
if (code!=0){
// remove history
cmd2=new String[cmdLine.length-1];
System.arraycopy( cmdLine, 0, cmd2, 0, cmd2.length ); /** remove --split **/
cmd2[cmd2.length-1]="--list"; //$NON-NLS-1$
try (StringWriter sw2=new StringWriter()) {
code=new ProcessRunner().executeBlocking( workingDir, sw2, null, cmd2 );
if (code==0){
cmd2=new String[cmdLine.length-1];
System.arraycopy( cmdLine, 0, cmd2, 0, cmd2.length-1 );
cmd2[cmd2.length-1]="--split"; //$NON-NLS-1$
cmdLine=cmd2;
}
}
}
root=new TestResult( configuration.getName() );
/** parse list **/
HTFParser.parseTestList( root, file , p);
suite=new TestSuite( root );
for (ITestListener tl:TestListenerManager.getContributors().values()){
tl.start(suite );
}
} catch (Exception ioe){
throw new CoreException( new Status(IStatus.ERROR,HaskellDebugCore.getPluginId(),ioe.getLocalizedMessage(),ioe) );
} finally {
file.delete();
}
/**
* create the parse thread
*/
parseThread=new ParseThread( fname, p );
/**
* super class will create the proper process
*/
return super.createProcess( configuration, mode, launch, location, cmdLine,
workingDir );
}
/**
* delay between checks of the JSON output file
*/
private static final long DELAY=1000;
/**
* thread regularly reading the HTF JSON output file
* @author JP Moresmau
*
*/
private class ParseThread extends Thread{
/**
* continuation flag
*/
private boolean goOn=true;
/**
* last modification date of the file we've checked
*/
// private final long lastCheck=0;
private final String fname;
private final IProject project;
private int idx=0;
/**
*
*/
public ParseThread(final String f,final IProject p) {
setDaemon( true );
this.fname=f;
this.project=p;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while(goOn){
/*long lm=file.lastModified();
if (lm>lastCheck){
lastCheck=lm;
parseOutfile( file, project );
}*/
File f=new File(fname+idx);
while (f.exists()){
try {
parseOutfile( f, project );
} catch (Exception e){
HaskellDebugCore.log( e.getLocalizedMessage(), e );
} finally {
f.delete();
}
idx++;
f=new File(fname+idx);
}
try {
Thread.sleep( DELAY );
} catch (InterruptedException ie){
//noop
}
}
}
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate#postProcessCreation(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.debug.core.model.IProcess)
*/
@Override
protected void postProcessCreation( final ILaunchConfiguration configuration,
final String mode, final ILaunch launch, final IProcess process ) {
if (parseThread!=null){
parseThread.start();
}
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate#preProcessCreation(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, java.util.Map)
*/
@Override
protected void preProcessCreation( final ILaunchConfiguration configuration,
final String mode, final ILaunch launch, final Map<String, String> processAttribs )
{
// NOOP
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate#preProcessDefinitionCreation(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch)
*/
@Override
protected void preProcessDefinitionCreation(
final ILaunchConfiguration configuration, final String mode, final ILaunch launch )
{
// NOOP
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.AbstractHaskellLaunchDelegate#postProcessFinished(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
protected void postProcessFinished( final ILaunchConfiguration configuration )
throws CoreException {
/**
* close the parse thread and checks if we need to reparse the file
*/
int idx=0;
if (parseThread!=null){
parseThread.goOn=false;
parseThread.interrupt();
idx=parseThread.idx;
parseThread=null;
}
final String fname = getFilename();
File f = new File( fname+idx );
IProject p=getProject( configuration );
/** parse all output, notify listeners at the end to avoid flicker **/
while (f.exists()){
try {
//parseOutfile( f, p );
HTFParser.parseTestOutput( root, f , p);
} catch (Exception ioe){
throw new CoreException( new Status(IStatus.ERROR,HaskellDebugCore.getPluginId(),ioe.getLocalizedMessage(),ioe) );
} finally {
f.delete();
}
idx++;
f=new File(fname+idx);
}
suite.reset();
for (ITestListener tl:TestListenerManager.getContributors().values()){
tl.end(suite );
}
new File(fname).delete();
//
// if (file.lastModified()>lastCheck){
//
// try {
// parseOutfile( file, p );
// } catch (Exception ioe){
// throw new CoreException( new Status(IStatus.ERROR,HaskellDebugCore.getPluginId(),ioe.getLocalizedMessage(),ioe) );
// } finally {
// file.delete();
// }
// }
}
/**
* parse the JSON output file
* @param f
* @param p
* @throws Exception
*/
private void parseOutfile(final File f,final IProject p) throws Exception{
if (f.exists()){
HTFParser.parseTestOutput( root, f , p);
suite.reset();
for (ITestListener tl:TestListenerManager.getContributors().values()){
tl.update(suite );
}
}
}
/* (non-Javadoc)
* @see net.sf.eclipsefp.haskell.debug.core.internal.launch.ExecutableOrTestSuiteHaskellLaunchDelegate#getExtraArguments()
*/
@Override
protected String getExtraArguments() {
return "--json --output-file=\""+getFilename()+"\" --colors=NO --history=\""+getHistoryFilename()+"\" --split"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}