You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pig.apache.org by pr...@apache.org on 2010/05/06 02:05:27 UTC
svn commit: r941551 - in /hadoop/pig/trunk: CHANGES.txt
src/org/apache/pig/Main.java src/org/apache/pig/tools/grunt/Grunt.java
src/org/apache/pig/tools/grunt/GruntParser.java
test/org/apache/pig/test/TestGrunt.java test/org/apache/pig/test/Util.java
Author: pradeepkth
Date: Thu May 6 00:05:26 2010
New Revision: 941551
URL: http://svn.apache.org/viewvc?rev=941551&view=rev
Log:
PIG-1211: Pig script runs half way after which it reports syntax error (pradeepkth)
Modified:
hadoop/pig/trunk/CHANGES.txt
hadoop/pig/trunk/src/org/apache/pig/Main.java
hadoop/pig/trunk/src/org/apache/pig/tools/grunt/Grunt.java
hadoop/pig/trunk/src/org/apache/pig/tools/grunt/GruntParser.java
hadoop/pig/trunk/test/org/apache/pig/test/TestGrunt.java
hadoop/pig/trunk/test/org/apache/pig/test/Util.java
Modified: hadoop/pig/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/CHANGES.txt?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/CHANGES.txt (original)
+++ hadoop/pig/trunk/CHANGES.txt Thu May 6 00:05:26 2010
@@ -54,6 +54,9 @@ PIG-1309: Map-side Cogroup (ashutoshc)
BUG FIXES
+PIG-1211: Pig script runs half way after which it reports syntax error
+(pradeepkth)
+
PIG-1401: "explain -script <script file>" executes grunt commands like
run/dump/copy etc - explain -script should not execute any grunt command and
only explain the query plans (pradeepkth)
Modified: hadoop/pig/trunk/src/org/apache/pig/Main.java
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/src/org/apache/pig/Main.java?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/src/org/apache/pig/Main.java (original)
+++ hadoop/pig/trunk/src/org/apache/pig/Main.java Thu May 6 00:05:26 2010
@@ -80,7 +80,9 @@ public class Main
private static final String JAR = "jar";
private static final String VERBOSE = "verbose";
- private enum ExecMode {STRING, FILE, SHELL, UNKNOWN};
+ private enum ExecMode {STRING, FILE, SHELL, UNKNOWN}
+
+ private static boolean checkScriptOnly = false;
/**
* The Main-Class for the Pig Jar that will provide a shell and setup a classpath appropriate
@@ -114,7 +116,7 @@ public static void main(String args[])
CmdLineParser opts = new CmdLineParser(args);
opts.registerOpt('4', "log4jconf", CmdLineParser.ValueExpected.REQUIRED);
opts.registerOpt('b', "brief", CmdLineParser.ValueExpected.NOT_ACCEPTED);
- opts.registerOpt('c', "cluster", CmdLineParser.ValueExpected.REQUIRED);
+ opts.registerOpt('c', "check", CmdLineParser.ValueExpected.NOT_ACCEPTED);
opts.registerOpt('d', "debug", CmdLineParser.ValueExpected.REQUIRED);
opts.registerOpt('e', "execute", CmdLineParser.ValueExpected.NOT_ACCEPTED);
opts.registerOpt('f', "file", CmdLineParser.ValueExpected.REQUIRED);
@@ -139,11 +141,6 @@ public static void main(String args[])
if(execTypeString!=null && execTypeString.length()>0){
execType = PigServer.parseExecType(execTypeString);
}
- String cluster = "local";
- String clusterConfigured = properties.getProperty("cluster");
- if(clusterConfigured != null && clusterConfigured.length() > 0){
- cluster = clusterConfigured;
- }
//by default warning aggregation is on
properties.setProperty("aggregate.warning", ""+true);
@@ -172,12 +169,7 @@ public static void main(String args[])
break;
case 'c':
- // Needed away to specify the cluster to run the MR job on
- // Bug 831708 - fixed
- String clusterParameter = opts.getValStr();
- if (clusterParameter != null && clusterParameter.length() > 0) {
- cluster = clusterParameter;
- }
+ checkScriptOnly = true;
break;
case 'd':
@@ -314,7 +306,7 @@ public static void main(String args[])
// run parameter substitution preprocessor first
substFile = file + ".substituted";
- pin = runParamPreprocessor(in, params, paramFiles, substFile, debug || dryrun);
+ pin = runParamPreprocessor(in, params, paramFiles, substFile, debug || dryrun || checkScriptOnly);
if (dryrun) {
log.info("Dry run completed. Substituted pig script is at " + substFile);
return;
@@ -334,12 +326,27 @@ public static void main(String args[])
grunt = new Grunt(pin, pigContext);
gruntCalled = true;
- int results[] = grunt.exec();
- rc = getReturnCodeForStats(results);
+
+ if(checkScriptOnly) {
+ grunt.checkScript(substFile);
+ System.err.println(file + " syntax OK");
+ rc = 0;
+ } else {
+ int results[] = grunt.exec();
+ rc = getReturnCodeForStats(results);
+ }
+
return;
}
case STRING: {
+ if(checkScriptOnly) {
+ System.err.println("ERROR:" +
+ "-c (-check) option is only valid " +
+ "when executing pig with a pig script file)");
+ rc = 2; // failure
+ return;
+ }
// Gather up all the remaining arguments into a string and pass them into
// grunt.
StringBuffer sb = new StringBuffer();
@@ -366,6 +373,13 @@ public static void main(String args[])
// run grunt interactive.
String remainders[] = opts.getRemainingArgs();
if (remainders == null) {
+ if(checkScriptOnly) {
+ System.err.println("ERROR:" +
+ "-c (-check) option is only valid " +
+ "when executing pig with a pig script file)");
+ rc = 2; // failure
+ return;
+ }
// Interactive
mode = ExecMode.SHELL;
ConsoleReader reader = new ConsoleReader(System.in, new OutputStreamWriter(System.out));
@@ -383,15 +397,14 @@ public static void main(String args[])
} else {
// They have a pig script they want us to run.
if (remainders.length > 1) {
- throw new RuntimeException("You can only run one pig script "
- + "at a time from the command line.");
+ throw new RuntimeException("Encountered unexpected arguments on command line - please check the command line.");
}
mode = ExecMode.FILE;
in = new BufferedReader(new FileReader(remainders[0]));
// run parameter substitution preprocessor first
substFile = remainders[0] + ".substituted";
- pin = runParamPreprocessor(in, params, paramFiles, substFile, debug || dryrun);
+ pin = runParamPreprocessor(in, params, paramFiles, substFile, debug || dryrun || checkScriptOnly);
if (dryrun){
log.info("Dry run completed. Substituted pig script is at " + substFile);
return;
@@ -411,8 +424,15 @@ public static void main(String args[])
grunt = new Grunt(pin, pigContext);
gruntCalled = true;
- int[] results = grunt.exec();
- rc = getReturnCodeForStats(results);
+
+ if(checkScriptOnly) {
+ grunt.checkScript(substFile);
+ System.err.println(remainders[0] + " syntax OK");
+ rc = 0;
+ } else {
+ int results[] = grunt.exec();
+ rc = getReturnCodeForStats(results);
+ }
return;
}
Modified: hadoop/pig/trunk/src/org/apache/pig/tools/grunt/Grunt.java
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/src/org/apache/pig/tools/grunt/Grunt.java?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/src/org/apache/pig/tools/grunt/Grunt.java (original)
+++ hadoop/pig/trunk/src/org/apache/pig/tools/grunt/Grunt.java Thu May 6 00:05:26 2010
@@ -23,6 +23,7 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.IOException;
import java.io.FileOutputStream;
+import java.util.ArrayList;
import jline.ConsoleReader;
import jline.Completor;
@@ -93,5 +94,20 @@ public class Grunt
throw (t);
}
}
+
+ public void checkScript(String scriptFile) throws Throwable {
+ boolean verbose = "true".equalsIgnoreCase(pig.getPigContext().getProperties().getProperty("verbose"));
+ try {
+ parser.setInteractive(false);
+ boolean dontPrintOutput = true;
+ parser.processExplain(null, scriptFile, false, "text", null,
+ new ArrayList<String>(), new ArrayList<String>(),
+ dontPrintOutput);
+ } catch (Throwable t) {
+ LogUtils.writeLog(t, pig.getPigContext().getProperties().getProperty("pig.logfile"),
+ log, verbose, "Pig Stack Trace");
+ throw (t);
+ }
+ }
}
Modified: hadoop/pig/trunk/src/org/apache/pig/tools/grunt/GruntParser.java
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/src/org/apache/pig/tools/grunt/GruntParser.java?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/src/org/apache/pig/tools/grunt/GruntParser.java (original)
+++ hadoop/pig/trunk/src/org/apache/pig/tools/grunt/GruntParser.java Thu May 6 00:05:26 2010
@@ -31,6 +31,7 @@ import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
+import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -230,7 +231,7 @@ public class GruntParser extends PigScri
}
mPigServer.dumpSchema(alias);
} else {
- log.warn("'describe' statement is ignored while processing explain");
+ log.warn("'describe' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -238,6 +239,15 @@ public class GruntParser extends PigScri
protected void processExplain(String alias, String script, boolean isVerbose,
String format, String target,
List<String> params, List<String> files)
+ throws IOException, ParseException {
+ processExplain(alias, script, isVerbose, format, target, params, files,
+ false);
+ }
+
+ protected void processExplain(String alias, String script, boolean isVerbose,
+ String format, String target,
+ List<String> params, List<String> files,
+ boolean dontPrintOutput)
throws IOException, ParseException {
if (null != mExplain) {
@@ -267,7 +277,7 @@ public class GruntParser extends PigScri
}
mExplain.mLast = true;
- explainCurrentBatch();
+ explainCurrentBatch(dontPrintOutput);
} finally {
if (script != null) {
@@ -278,9 +288,30 @@ public class GruntParser extends PigScri
}
protected void explainCurrentBatch() throws IOException {
- PrintStream lp = System.out;
- PrintStream pp = System.out;
- PrintStream ep = System.out;
+ explainCurrentBatch(false);
+ }
+
+ /**
+ * A {@link PrintStream} implementation which does not write anything
+ * Used with '-check' command line option to pig Main
+ * (through {@link GruntParser#explainCurrentBatch(boolean) } )
+ */
+ static class NullPrintStream extends PrintStream {
+ public NullPrintStream(String fileName) throws FileNotFoundException {
+ super(fileName);
+ }
+ @Override
+ public void write(byte[] buf, int off, int len) {}
+ @Override
+ public void write(int b) {}
+ @Override
+ public void write(byte [] b) {}
+ }
+
+ protected void explainCurrentBatch(boolean dontPrintOutput) throws IOException {
+ PrintStream lp = (dontPrintOutput) ? new NullPrintStream("dummy") : System.out;
+ PrintStream pp = (dontPrintOutput) ? new NullPrintStream("dummy") : System.out;
+ PrintStream ep = (dontPrintOutput) ? new NullPrintStream("dummy") : System.out;
if (!(mExplain.mLast && mExplain.mCount == 0)) {
if (mPigServer.isBatchEmpty()) {
@@ -324,7 +355,7 @@ public class GruntParser extends PigScri
if(mExplain == null) { // process only if not in "explain" mode
mPigServer.printAliases();
} else {
- log.warn("'aliases' statement is ignored while processing explain");
+ log.warn("'aliases' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -376,7 +407,7 @@ public class GruntParser extends PigScri
loadScript(script, false, mLoadOnly, params, files);
}
} else {
- log.warn("'run/exec' statement is ignored while processing explain");
+ log.warn("'run/exec' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -519,7 +550,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to Cat: " + path, e);
}
} else {
- log.warn("'cat' statement is ignored while processing explain");
+ log.warn("'cat' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -554,7 +585,7 @@ public class GruntParser extends PigScri
: (path)), e);
}
} else {
- log.warn("'cd' statement is ignored while processing explain");
+ log.warn("'cd' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -569,7 +600,7 @@ public class GruntParser extends PigScri
System.out.println(TupleFormat.format(t));
}
} else {
- log.warn("'dump' statement is ignored while processing explain");
+ log.warn("'dump' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -579,7 +610,7 @@ public class GruntParser extends PigScri
if(mExplain == null) { // process only if not in "explain" mode
mPigServer.getExamples(alias);
} else {
- log.warn("'illustrate' statement is ignored while processing explain");
+ log.warn("'illustrate' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -639,7 +670,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to LS on " + path, e);
}
} else {
- log.warn("'ls' statement is ignored while processing explain");
+ log.warn("'ls' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -660,7 +691,7 @@ public class GruntParser extends PigScri
if(mExplain == null) { // process only if not in "explain" mode
System.out.println(mDfs.getActiveContainer().toString());
} else {
- log.warn("'pwd' statement is ignored while processing explain");
+ log.warn("'pwd' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -704,7 +735,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to move " + src + " to " + dst, e);
}
} else {
- log.warn("'mv' statement is ignored while processing explain");
+ log.warn("'mv' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -725,7 +756,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to copy " + src + " to " + dst, e);
}
} else {
- log.warn("'cp' statement is ignored while processing explain");
+ log.warn("'cp' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -746,7 +777,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to copy " + src + "to (locally) " + dst, e);
}
} else {
- log.warn("'copyToLocal' statement is ignored while processing explain");
+ log.warn("'copyToLocal' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -767,7 +798,7 @@ public class GruntParser extends PigScri
throw new IOException("Failed to copy (loally) " + src + "to " + dst, e);
}
} else {
- log.warn("'copyFromLocal' statement is ignored while processing explain");
+ log.warn("'copyFromLocal' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -778,7 +809,7 @@ public class GruntParser extends PigScri
ContainerDescriptor dirDescriptor = mDfs.asContainer(dir);
dirDescriptor.create();
} else {
- log.warn("'mkdir' statement is ignored while processing explain");
+ log.warn("'mkdir' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -816,7 +847,7 @@ public class GruntParser extends PigScri
dfsPath.delete();
}
} else {
- log.warn("'rm/rmf' statement is ignored while processing explain");
+ log.warn("'rm/rmf' statement is ignored while processing 'explain -script' or '-check'");
}
}
@@ -832,7 +863,7 @@ public class GruntParser extends PigScri
throw new IOException(e);
}
} else {
- log.warn("'fs' statement is ignored while processing explain");
+ log.warn("'fs' statement is ignored while processing 'explain -script' or '-check'");
}
}
Modified: hadoop/pig/trunk/test/org/apache/pig/test/TestGrunt.java
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/test/org/apache/pig/test/TestGrunt.java?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/test/org/apache/pig/test/TestGrunt.java (original)
+++ hadoop/pig/trunk/test/org/apache/pig/test/TestGrunt.java Thu May 6 00:05:26 2010
@@ -29,6 +29,7 @@ import org.apache.pig.PigException;
import org.apache.pig.PigServer;
import org.apache.pig.backend.executionengine.ExecException;
import org.apache.pig.impl.PigContext;
+import org.apache.pig.test.Util.ProcessReturnInfo;
import org.apache.pig.tools.grunt.Grunt;
import org.apache.pig.tools.parameters.ParameterSubstitutionPreprocessor;
import org.apache.pig.tools.pigscript.parser.ParseException;
@@ -41,6 +42,7 @@ import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.StringWriter;
+import java.util.ArrayList;
public class TestGrunt extends TestCase {
MiniCluster cluster = MiniCluster.buildCluster();
@@ -494,7 +496,8 @@ public class TestGrunt extends TestCase
"'mkdir'", "'illustrate'", "'run/exec'", "'fs'", "'aliases'",
"'mv'", "'dump'" };
for (String c : cmds) {
- String expected = c + " statement is ignored while processing explain";
+ String expected = c + " statement is ignored while processing " +
+ "'explain -script' or '-check'";
assertTrue("Checking if " + gruntLoggingContents + " contains " +
expected, gruntLoggingContents.contains(expected));
}
@@ -934,4 +937,90 @@ public class TestGrunt extends TestCase
assertTrue(e.getMessage().contains("Encountered: \"^\" (94), after : \"\\\"\""));
}
}
+
+ public void testCheckScript() throws Throwable {
+ // a query which has grunt commands intermixed with pig statements - this
+ // should pass through successfully with the check and all the grunt commands
+ // should be ignored during the check.
+ String query = "rmf input-copy.txt; cat 'foo'; a = load '1.txt' ; " +
+ "aliases;illustrate a; copyFromLocal foo bar; copyToLocal foo bar; " +
+ "describe a; mkdir foo; run bar.pig; exec bar.pig; cp foo bar; " +
+ "explain a;cd 'bar'; pwd; ls ; fs -ls ; fs -rmr foo; mv foo bar; " +
+ "dump a;store a into 'input-copy.txt' ; a = load '2.txt' as (b);" +
+ "explain a; rm foo; store a into 'bar';";
+
+ String[] cmds = new String[] { "'rm/rmf'", "'cp'", "'cat'", "'cd'", "'pwd'",
+ "'copyFromLocal'", "'copyToLocal'", "'describe'", "'ls'",
+ "'mkdir'", "'illustrate'", "'run/exec'", "'fs'", "'aliases'",
+ "'mv'", "'dump'" };
+ ArrayList<String> msgs = new ArrayList<String>();
+ for (String c : cmds) {
+ msgs.add(c + " statement is ignored while processing " +
+ "'explain -script' or '-check'");
+ }
+ validate(query, true, msgs.toArray(new String[0]));
+ }
+
+ public void testCheckScriptSyntaxErr() throws Throwable {
+ // a query which has grunt commands intermixed with pig statements - this
+ // should fail with the -check option with a syntax error
+
+ // the query has a typo - chararay instead of chararray
+ String query = "a = load '1.txt' ; fs -rmr foo; mv foo bar; dump a;" +
+ "store a into 'input-copy.txt' ; dump a; a = load '2.txt' as " +
+ "(b:chararay);explain a; rm foo; store a into 'bar';";
+
+ String[] cmds = new String[] { "'fs'", "'mv'", "'dump'" };
+ ArrayList<String> msgs = new ArrayList<String>();
+ for (String c : cmds) {
+ msgs.add(c + " statement is ignored while processing " +
+ "'explain -script' or '-check'");
+ }
+ msgs.add("Error during parsing");
+ validate(query, false, msgs.toArray(new String[0]));
+ }
+
+ public void testCheckScriptTypeCheckErr() throws Throwable {
+ // a query which has grunt commands intermixed with pig statements - this
+ // should fail with the -check option with a type checking error
+
+ // the query has incompatible types in bincond
+ String query = "a = load 'foo.pig' as (s:chararray); dump a; explain a; " +
+ "store a into 'foobar'; b = foreach a generate " +
+ "(s == 2 ? 1 : 2.0); store b into 'bar';";
+
+ String[] cmds = new String[] { "'dump'" };
+ ArrayList<String> msgs = new ArrayList<String>();
+ for (String c : cmds) {
+ msgs.add(c + " statement is ignored while processing " +
+ "'explain -script' or '-check'");
+ }
+ msgs.add("In alias b, incompatible types in EqualTo Operator");
+ validate(query, false, msgs.toArray(new String[0]));
+ }
+
+ private void validate(String query, boolean syntaxOk,
+ String[] logMessagesToCheck) throws Throwable {
+ File scriptFile = Util.createFile(new String[] { query});
+ String scriptFileName = scriptFile.getAbsolutePath();
+ String cmd = "java -cp " + System.getProperty("java.class.path") +
+ " org.apache.pig.Main -x local -c " + scriptFileName;
+
+ ProcessReturnInfo pri = Util.executeJavaCommandAndReturnInfo(cmd);
+ for (String msg : logMessagesToCheck) {
+ assertTrue("Checking if " + pri.stderrContents + " contains " +
+ msg, pri.stderrContents.contains(msg));
+ }
+ if(syntaxOk) {
+ assertTrue("Checking that the syntax OK message was printed on " +
+ "stderr <" + pri.stderrContents + ">",
+ pri.stderrContents.contains("syntax OK"));
+ } else {
+ assertFalse("Checking that the syntax OK message was NOT printed on " +
+ "stderr <" + pri.stderrContents + ">",
+ pri.stderrContents.contains("syntax OK"));
+ }
+ }
+
+
}
Modified: hadoop/pig/trunk/test/org/apache/pig/test/Util.java
URL: http://svn.apache.org/viewvc/hadoop/pig/trunk/test/org/apache/pig/test/Util.java?rev=941551&r1=941550&r2=941551&view=diff
==============================================================================
--- hadoop/pig/trunk/test/org/apache/pig/test/Util.java (original)
+++ hadoop/pig/trunk/test/org/apache/pig/test/Util.java Thu May 6 00:05:26 2010
@@ -27,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@@ -483,17 +484,49 @@ public class Util {
}
public static int executeJavaCommand(String cmd) throws Exception {
+ return executeJavaCommandAndReturnInfo(cmd).exitCode;
+ }
+
+
+ public static ProcessReturnInfo executeJavaCommandAndReturnInfo(String cmd)
+ throws Exception {
String javaHome = System.getenv("JAVA_HOME");
if(javaHome != null) {
String fileSeparator = System.getProperty("file.separator");
cmd = javaHome + fileSeparator + "bin" + fileSeparator + cmd;
}
Process cmdProc = Runtime.getRuntime().exec(cmd);
-
+ ProcessReturnInfo pri = new ProcessReturnInfo();
+ pri.stdoutContents = getContents(cmdProc.getInputStream());
+ pri.stderrContents = getContents(cmdProc.getErrorStream());
cmdProc.waitFor();
+ pri.exitCode = cmdProc.exitValue();
+ return pri;
+ }
+
+ private static String getContents(InputStream istr) throws IOException {
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader(istr));
+ String s = "";
+ String line;
+ while ( (line = br.readLine()) != null) {
+ s += line + "\n";
+ }
+ return s;
- return cmdProc.exitValue();
}
+ public static class ProcessReturnInfo {
+ public int exitCode;
+ public String stderrContents;
+ public String stdoutContents;
+
+ @Override
+ public String toString() {
+ return "[Exit code: " + exitCode + ", stdout: <" + stdoutContents + ">, " +
+ "stderr: <" + stderrContents + ">";
+ }
+ }
+
static public boolean deleteDirectory(File path) {
if(path.exists()) {
File[] files = path.listFiles();