/**
* 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.test;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.sf.eclipsefp.haskell.buildwrapper.types.Location;
import net.sf.eclipsefp.haskell.util.FileUtil;
import org.eclipse.core.resources.IProject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Parses the HTF JSON output file
* @author JP Moresmau
*
*/
public class HTFParser {
private static final String TESTS="tests"; //$NON-NLS-1$
private static final String LOCATION="location"; //$NON-NLS-1$
private static final String PATH="path"; //$NON-NLS-1$
private static final String LINE="line"; //$NON-NLS-1$
private static final String FILE="file"; //$NON-NLS-1$
private static final String TYPE="type"; //$NON-NLS-1$
private static final String TEST_END="test-end"; //$NON-NLS-1$
private static final String TEST_START="test-start"; //$NON-NLS-1$
private static final String RESULT="result"; //$NON-NLS-1$
private static final String RESULT_FAIL="fail"; //$NON-NLS-1$
private static final String RESULT_PASS="pass"; //$NON-NLS-1$
private static final String MESSAGE="message"; //$NON-NLS-1$
private static final String TEST="test"; //$NON-NLS-1$
private static final String WALL_TIME="wallTime"; //$NON-NLS-1$
private static final String TEST_RESULTS="test-results"; //$NON-NLS-1$
private static final String SEP="\n;;\n"; //$NON-NLS-1$
/**
* parse the test list (output of --list flag)
* @param root the root test result
* @param f the JSON file
* @param p the project we're in, to calculate locations
* @throws IOException
* @throws JSONException
*/
public static void parseTestList(final TestResult root,final File f,final IProject p) throws IOException, JSONException{
String s=FileUtil.getContents( f,FileUtil.UTF8 );
if (s.length()==0){
return;
}
Map<String,TestResult> parents=new HashMap<>();
parents.put( new JSONArray().toString(), root );
int ix=s.indexOf(SEP);
if (ix>-1){
s=s.substring( 0,ix );
}
JSONObject obj=new JSONObject( s );
JSONArray arr=obj.optJSONArray( TESTS );
if (arr!=null){
for (int a=0;a<arr.length();a++){
JSONObject tcObj=arr.optJSONObject( a );
newTestResult(tcObj,parents,p);
}
}
}
/**
* create a new test result
* @param tcObj
* @param parents
* @param p
* @return
* @throws JSONException
*/
private static TestResult newTestResult(final JSONObject tcObj,final Map<String,TestResult> parents,final IProject p) throws JSONException{
if (tcObj!=null){
JSONArray qn=tcObj.optJSONArray( PATH );
if (qn!=null){
String name=qn.getString( qn.length()-1 );
TestResult tr=new TestResult( name );
parseLocation( tr,tcObj );
tr.setProject( p );
tr.setStatus( TestStatus.PENDING );
parents.put(qn.toString() ,tr);
getParent( qn, parents ).addChild( tr );
return tr;
}
}
return null;
}
/**
* parse the location of the test
* @param tr
* @param tcObj
* @throws JSONException
*/
private static void parseLocation(final TestResult tr,final JSONObject tcObj) throws JSONException{
JSONObject locObj=tcObj.optJSONObject( LOCATION );
if (locObj!=null){
int line=locObj.getInt( LINE );
Location loc=new Location(locObj.getString(FILE),line,0,line,0);
tr.setLocation( loc );
}
}
/**
* get the parent test result
* @param qn
* @param parents
* @return
* @throws JSONException
*/
private static TestResult getParent(final JSONArray qn,final Map<String,TestResult> parents) throws JSONException{
JSONArray qnParent=qn.removeLast();
String key=qnParent.toString();
TestResult parent=parents.get( key );
if (parent==null){
parent=new TestResult(qnParent.getString( qnParent.length()-1 ));
parents.put(key,parent);
getParent( qnParent, parents ).addChild(parent);
}
return parent;
}
/**
* parse the test output file
* @param root the root test result
* @param f the output file
* @param p the project, for resolving locations
* @throws IOException
* @throws JSONException
*/
public static void parseTestOutput(final TestResult root,final File f,final IProject p) throws IOException, JSONException{
String s=FileUtil.getContents( f,FileUtil.UTF8 );
if (s.length()==0){
return;
}
Map<String,TestResult> parents=new HashMap<>();
fillParents(root, new JSONArray(),parents );
//int ix=s.indexOf(SEP);
//int start=0;
//TestResult last=null;
//while (ix>-1){
//String obj=s.substring( start,ix ).trim();
parse1TestOutput( s, parents, p );
// start=ix+SEP.length();
// ix=s.indexOf(SEP,start);
//}
}
private static void fillParents(final TestResult tr,final JSONArray arr,final Map<String,TestResult> parents){
parents.put( arr.toString(), tr );
for (TestResult c:tr.getChildren()){
JSONArray ca=new JSONArray();
ca.putAll( arr );
ca.put( c.getName() );
fillParents( c, ca, parents );
}
}
/**
* parse 1 specific output
* @param sobj the JSON string
* @param parents the parents map
* @param p the project for locations
* @throws JSONException
*/
private static void parse1TestOutput(final String sobj,final Map<String,TestResult> parents,final IProject p) throws JSONException{
JSONObject rObj=new JSONObject(sobj);
String tp=rObj.getString( TYPE );
if (tp.equals( TEST_END )){
JSONObject tcObj=rObj.getJSONObject( TEST );
TestResult last=newTestResult( tcObj, parents, p );
if (last!=null){
String r=rObj.getString( RESULT );
if (RESULT_PASS.equals(r)){
last.setStatus( TestStatus.OK );
} else if (RESULT_FAIL.equals(r)){
last.setStatus( TestStatus.FAILURE );
} else{
last.setStatus( TestStatus.ERROR );
}
last.setText( rObj.optString(MESSAGE) );
if (!TestStatus.OK.equals(last.getStatus())){
parseLocation( last, rObj );
}
last.setWallTime( rObj.optLong( WALL_TIME, 0 ) );
}
} else if (tp.equals( TEST_START )){
JSONObject tcObj=rObj.getJSONObject( TEST );
TestResult tr=newTestResult( tcObj, parents, p );
tr.setStatus( TestStatus.RUNNING ); // running until we get the corresponding test-end
//return tr;
} else if (tp.equals( TEST_RESULTS )){
TestResult root=parents.get( new JSONArray().toString() );
root.setWallTime( rObj.optLong( WALL_TIME, 0 ) );
}
// return null;
}
/*
{"test":{"flatName":"Main:nonEmpty","location":{"file":"test\\Main.hs","line":12},"path":["Main","nonEmpty"],"sort":"unit-test"},"type":"test-start"}
;;
{"result":"fail","message":"assertEqual failed at test\\\\Main.hs:13\n* expected: [3, 2, 1]\n* but got: [3]\n* diff: \nC [3\nF , 2, 1\nC ]","test":{"flatName":"Main:nonEmpty","location":{"file":"test\\Main.hs","line":12},"path":["Main","nonEmpty"],"sort":"unit-test"},"wallTime":1,"type":"test-end","location":{"file":"test\\\\Main.hs","line":13}}
;;
{"test":{"flatName":"Main:empty","location":{"file":"test\\Main.hs","line":15},"path":["Main","empty"],"sort":"unit-test"},"type":"test-start"}
;;
{"result":"pass","message":"","test":{"flatName":"Main:empty","location":{"file":"test\\Main.hs","line":15},"path":["Main","empty"],"sort":"unit-test"},"wallTime":0,"type":"test-end","location":null}
;;
{"test":{"flatName":"Main:reverse","location":{"file":"test\\Main.hs","line":18},"path":["Main","reverse"],"sort":"quickcheck-property"},"type":"test-start"}
;;
{"result":"fail","message":"Falsifiable (after 3 tests and 1 shrink): \n[0,0]\nReplay argument: \"Just (275851486 2147483396,2)\"","test":{"flatName":"Main:reverse","location":{"file":"test\\Main.hs","line":18},"path":["Main","reverse"],"sort":"quickcheck-property"},"wallTime":0,"type":"test-end","location":null}
;;
{"failures":2,"passed":1,"pending":0,"wallTime":4,"errors":0,"type":"test-results"}
;;
*/
}