Serialise the CTS runtime command-line in CTS results

Bug: 28076074

Change-Id: Ib696ded764f55180172d0c585f90335b8ad22e90
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index 3591668..522d372 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -42,6 +42,8 @@
     private static final String START_TIME_MS = "START_TIME_MS";
     private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
     private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
+    private static final String COMMAND_LINE_ARGS = "command_line_args";
+    private static final String RETRY_COMMAND_LINE_ARGS = "retry_command_line_args";
     private final IBuildInfo mBuildInfo;
     private boolean mInitialized = false;
 
@@ -103,6 +105,20 @@
         return mBuildInfo;
     }
 
+    public void setRetryCommandLineArgs(String commandLineArgs) {
+        mBuildInfo.addBuildAttribute(RETRY_COMMAND_LINE_ARGS, commandLineArgs);
+    }
+
+    public String getCommandLineArgs() {
+        if (mBuildInfo.getBuildAttributes().containsKey(RETRY_COMMAND_LINE_ARGS)) {
+            return mBuildInfo.getBuildAttributes().get(RETRY_COMMAND_LINE_ARGS);
+        } else {
+            // NOTE: this is a temporary workaround set in TestInvocation#invoke in tradefed.
+            // This will be moved to a separate method in a new invocation metadata class.
+            return mBuildInfo.getBuildAttributes().get(COMMAND_LINE_ARGS);
+        }
+    }
+
     public String getSuiteBuild() {
         return mBuildInfo.getBuildAttributes().get(SUITE_BUILD);
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 226bad2..01e6b2d 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -180,7 +180,7 @@
     }
 
     /**
-     * Create directory structure that where results and logs will be written.
+     * Create directory structure where results and logs will be written.
      */
     private void initializeResultDirectories() {
         info("Initializing result directory");
@@ -419,7 +419,7 @@
             File resultFile = ResultHandler.writeResults(mBuildHelper.getSuiteName(),
                     mBuildHelper.getSuiteVersion(), mBuildHelper.getSuitePlan(),
                     mBuildHelper.getSuiteBuild(), mResult, mResultDir, startTime,
-                    elapsedTime + startTime, mReferenceUrl);
+                    elapsedTime + startTime, mReferenceUrl, mBuildHelper.getCommandLineArgs());
             info("Test Result: %s", resultFile.getCanonicalPath());
 
             // Zip the full test results directory.
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 96ee568..f0c0b8b 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -20,6 +20,7 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
 import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
+import com.android.compatibility.common.tradefed.util.OptionHelper;
 import com.android.compatibility.common.util.AbiUtils;
 import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
@@ -31,6 +32,8 @@
 import com.android.compatibility.common.util.TestStatus;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ArgsOptionParser;
+import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
@@ -58,6 +61,8 @@
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -396,6 +401,26 @@
             }
             CLog.logAndDisplay(LogLevel.INFO, "Retrying session from: %s",
                     CompatibilityBuildHelper.getDirSuffix(result.getStartTime()));
+
+            String retryCommandLineArgs = result.getCommandLineArgs();
+            if (retryCommandLineArgs != null) {
+                // Copy the original command into the build helper so it can be serialized later
+                mBuildHelper.setRetryCommandLineArgs(retryCommandLineArgs);
+                try {
+                    // parse the command-line string from the result file and set options
+                    ArgsOptionParser parser = new ArgsOptionParser(this);
+                    parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, this));
+                    if (mModuleName != null) {
+                        // retry checks for tests to run via the include/exclude filters
+                        // so add the module to the include filter
+                        mIncludeFilters.add(new TestFilter(mAbiName, mModuleName,
+                            mTestName).toString());
+                    }
+                } catch (ConfigurationException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             // Append each test that failed or was not executed to the filters
             for (IModuleResult module : result.getModules()) {
                 for (ICaseResult testResultList : module.getResults()) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
new file mode 100644
index 0000000..79e2a29
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.tradefed.config.Option;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Set;
+
+/**
+ * Helper class for manipulating fields with @option annotations.
+ */
+public final class OptionHelper {
+
+    private OptionHelper() {}
+
+    /**
+     * Return the {@link List} of {@link Field} entries on the given object
+     * that have the {@link Option} annotation.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    private static List<Field> getFields(Object object) {
+        Field[] classFields = object.getClass().getDeclaredFields();
+        List<Field> optionFields = new ArrayList<Field>();
+
+        for (Field declaredField : classFields) {
+            // allow access to protected and private fields
+            declaredField.setAccessible(true);
+
+            // store type and values only in annotated fields
+            if (declaredField.isAnnotationPresent(Option.class)) {
+                optionFields.add(declaredField);
+            }
+        }
+        return optionFields;
+    }
+
+    /**
+     * Retrieve a {@link Set} of {@link Option} names present on the given
+     * object.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    static Set<String> getOptionNames(Object object) {
+        Set<String> options = new HashSet<String>();
+        List<Field> optionFields = getFields(object);
+
+        for (Field declaredField : optionFields) {
+            Option option = declaredField.getAnnotation(Option.class);
+            options.add(option.name());
+        }
+        return options;
+    }
+
+    /**
+     * Retrieve a {@link Set} of {@link Option} short names present on the given
+     * object.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    static Set<String> getOptionShortNames(Object object) {
+        Set<String> shortNames = new HashSet<String>();
+        List<Field> optionFields = getFields(object);
+
+        for (Field declaredField : optionFields) {
+            Option option = declaredField.getAnnotation(Option.class);
+            if (option.shortName() != Option.NO_SHORT_NAME) {
+                shortNames.add(String.valueOf(option.shortName()));
+            }
+        }
+        return shortNames;
+    }
+
+    /**
+     * Retrieve a {@link List} of {@link String} entries of the valid
+     * command-line options for the given {@link Object} from the given
+     * input {@link String}.
+     */
+    public static List<String> getValidCliArgs(String commandString, Object object) {
+        Set<String> optionNames = OptionHelper.getOptionNames(object);
+        Set<String> optionShortNames = OptionHelper.getOptionShortNames(object);
+        Map<String, String> optionMap = new HashMap <String, String>();
+        List<String> validCliArgs = new ArrayList<String>();
+
+        // get "-option/--option value" pairs from the command-line string
+        Pattern cliPattern = Pattern.compile("-[-\\w]+[ =]\\S+");
+        Matcher matcher = cliPattern.matcher(commandString);
+
+        while (matcher.find()) {
+            String match = matcher.group();
+            // split between the option name and value
+            String[] tokens = match.split("[ =]");
+            // remove initial hyphens from option args
+            String keyName = tokens[0].replaceFirst("^--?", "");
+
+            // add substrings only when the options are recognisable
+            if (optionShortNames.contains(keyName) || optionNames.contains(keyName)) {
+                validCliArgs.add(match);
+            }
+
+        }
+        return validCliArgs;
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 318960c..4339511 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -23,6 +23,7 @@
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
+import com.android.compatibility.common.tradefed.util.OptionHelperTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -40,6 +41,7 @@
         addTestSuite(CompatibilityConsoleTest.class);
         addTestSuite(ResultReporterTest.class);
         addTestSuite(CompatibilityTestTest.class);
+        addTestSuite(OptionHelperTest.class);
         addTestSuite(ModuleDefTest.class);
         addTestSuite(ModuleRepoTest.class);
         addTestSuite(PropertyCheckTest.class);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
index d968f5c..503c529 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -36,6 +36,7 @@
     private static final String ROOT_DIR_NAME = "root";
     private static final String BASE_DIR_NAME = "android-tests";
     private static final String TESTCASES = "testcases";
+    private static final String COMMAND_LINE_ARGS = "cts -m CtsModuleTestCases";
     private static final long START_TIME = 123456L;
 
     private File mRoot = null;
@@ -130,6 +131,16 @@
                 mHelper.getResultsDir().getAbsolutePath());
     }
 
+    public void testGetCommandLineArgs() {
+        assertNull(mHelper.getCommandLineArgs());
+        mBuild.addBuildAttribute("command_line_args", COMMAND_LINE_ARGS);
+        assertEquals(COMMAND_LINE_ARGS, mHelper.getCommandLineArgs());
+
+        mBuild.addBuildAttribute("command_line_args", "cts --retry 0");
+        mHelper.setRetryCommandLineArgs(COMMAND_LINE_ARGS);
+        assertEquals(COMMAND_LINE_ARGS, mHelper.getCommandLineArgs());
+    }
+
     /**
      * Sets the *_ROOT property of the build's installation location.
      *
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
new file mode 100644
index 0000000..7907d10
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link OptionHelper}
+ */
+public class OptionHelperTest extends TestCase {
+
+    private static final String TEST_CLASS = "test-class";
+    private static final String TEST_CLASS_SHORTNAME = "c";
+    private static final String TEST_NAME = "test-name";
+    private static final String TEST_SUITE = "test-suite";
+    private static final String TEST_SUITE_SHORTNAME = "s";
+
+    @Option(name = TEST_CLASS,
+            shortName = 'c',
+            importance = Importance.ALWAYS)
+    private String mTestClass = null;
+
+    @Option(name = TEST_NAME,
+            importance = Importance.ALWAYS)
+    private String mTestName = null;
+
+    @Option(name = TEST_SUITE,
+            shortName = 's',
+            importance = Importance.ALWAYS)
+    private String mTestSuite = null;
+
+    public void testGetOptionNames() throws Exception {
+        Set<String> optionNames = OptionHelper.getOptionNames(this);
+        List<String> expectedNames = Arrays.asList(TEST_CLASS, TEST_NAME, TEST_SUITE);
+        assertEquals("Missing option names", true, optionNames.containsAll(expectedNames));
+        assertEquals("Expected three elements", 3, optionNames.size());
+    }
+
+    public void testGetOptionShortNames() throws Exception {
+        Set<String> optionShortNames = OptionHelper.getOptionShortNames(this);
+        List<String> expectedShortNames = Arrays.asList(TEST_CLASS_SHORTNAME, TEST_SUITE_SHORTNAME);
+        assertEquals("Missing option shortnames", true,
+            optionShortNames.containsAll(expectedShortNames));
+        assertEquals("Expected two elements", 2, optionShortNames.size());
+    }
+
+    public void testGetValidCliArgs() throws Exception {
+        List<String> noValidNames = new ArrayList();
+        List<String> validSubset = Arrays.asList("--" + TEST_CLASS + " fooclass",
+            "-" + TEST_SUITE_SHORTNAME + " foosuite");
+        List<String> allValidNames = Arrays.asList("--" + TEST_CLASS + " fooclass",
+            "-" + TEST_SUITE_SHORTNAME + " foosuite", "--" + TEST_NAME + " footest");
+
+        assertEquals("Expected no valid names", noValidNames,
+            OptionHelper.getValidCliArgs("test --foo -b", this));
+        assertEquals("Expected one long name and one short name", validSubset,
+            OptionHelper.getValidCliArgs("test --" + TEST_CLASS + " fooclass -b fake"
+                + " -s foosuite", this));
+        assertEquals("Expected two long names and one short name", allValidNames,
+            OptionHelper.getValidCliArgs("test --" + TEST_CLASS + " fooclass -b fake"
+                + " -s foosuite " + "--" + TEST_NAME + " footest", this));
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
index ca241ef..927befd 100644
--- a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
@@ -84,4 +84,14 @@
      * Gets the {@link Map} of build info collected.
      */
     Map<String, String> getBuildInfo();
+
+    /**
+     *  Set the string containing the command line arguments to the run command.
+     */
+    void setCommandLineArgs(String setCommandLineArgs);
+
+    /**
+     * Retrieve the command line arguments to the run command.
+     */
+    String getCommandLineArgs();
 }
diff --git a/common/util/src/com/android/compatibility/common/util/InvocationResult.java b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
index 6166771..4cb8d5a 100644
--- a/common/util/src/com/android/compatibility/common/util/InvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
@@ -34,6 +34,7 @@
     private Map<String, String> mBuildInfo = new HashMap<>();
     private Set<String> mSerials = new HashSet<>();
     private String mTestPlan;
+    private String mCommandLineArgs;
 
     /**
      * {@inheritDoc}
@@ -143,4 +144,20 @@
     public Set<String> getDeviceSerials() {
         return mSerials;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setCommandLineArgs(String commandLineArgs) {
+        mCommandLineArgs = commandLineArgs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getCommandLineArgs() {
+        return mCommandLineArgs;
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index a07ecd9..21a3e39 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -55,6 +55,7 @@
     private static final String BUILD_PRODUCT = "build_product";
     private static final String BUILD_TAG = "Build";
     private static final String CASE_TAG = "TestCase";
+    private static final String COMMAND_LINE_ARGS = "command_line_args";
     private static final String DEVICES_ATTR = "devices";
     private static final String END_DISPLAY_TIME_ATTR = "end_display";
     private static final String END_TIME_ATTR = "end";
@@ -117,6 +118,7 @@
                 invocation.setStartTime(Long.valueOf(
                         parser.getAttributeValue(NS, START_TIME_ATTR)));
                 invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
+                invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
                 String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
                 for (String device : deviceList.split(",")) {
                     invocation.addDeviceSerial(device);
@@ -206,13 +208,14 @@
      * @param resultDir
      * @param startTime
      * @param referenceUrl A nullable string that can contain a URL to a related data
+     * @param commandLineArgs A string containing the arguments to the run command
      * @return The result file created.
      * @throws IOException
      * @throws XmlPullParserException
      */
     public static File writeResults(String suiteName, String suiteVersion, String suitePlan,
             String suiteBuild, IInvocationResult result, File resultDir,
-            long startTime, long endTime, String referenceUrl)
+            long startTime, long endTime, String referenceUrl, String commandLineArgs)
                     throws IOException, XmlPullParserException {
         int passed = result.countResults(TestStatus.PASS);
         int failed = result.countResults(TestStatus.FAIL);
@@ -236,6 +239,8 @@
         serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan);
         serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild);
         serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
+        serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs));
+
         if (referenceUrl != null) {
             serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl);
         }
@@ -365,4 +370,11 @@
         SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
         return dateFormat.format(new Date(time));
     }
+
+    /**
+     * When nullable is null, return an empty string. Otherwise, return the value in nullable.
+     */
+    private static String nullToEmpty(String nullable) {
+        return nullable == null ? "" : nullable;
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/TestFilter.java b/common/util/src/com/android/compatibility/common/util/TestFilter.java
index 47ed9ec..460e34f 100644
--- a/common/util/src/com/android/compatibility/common/util/TestFilter.java
+++ b/common/util/src/com/android/compatibility/common/util/TestFilter.java
@@ -94,15 +94,15 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
         if (mAbi != null) {
-            sb.append(mAbi);
+            sb.append(mAbi.trim());
             sb.append(" ");
         }
         if (mName != null) {
-            sb.append(mName);
+            sb.append(mName.trim());
         }
         if (mTest != null) {
             sb.append(" ");
-            sb.append(mTest);
+            sb.append(mTest.trim());
         }
         return sb.toString();
     }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index 41ee158..c5a61ad 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -80,6 +80,7 @@
     private static final String END_DISPLAY = "Fri Aug 20 15:13:04 PDT 2010";
 
     private static final String REFERENCE_URL="http://android.com";
+    private static final String COMMAND_LINE_ARGS = "cts -m CtsMyModuleTestCases";
     private static final String JOIN = "%s%s";
     private static final String XML_BASE =
             "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>" +
@@ -89,7 +90,7 @@
             "suite_plan=\"%s\" suite_build_number=\"%s\" report_version=\"%s\" " +
             "devices=\"%s\" host_name=\"%s\"" +
             "os_name=\"%s\" os_version=\"%s\" os_arch=\"%s\" java_vendor=\"%s\"" +
-            "java_version=\"%s\" reference_url=\"%s\">\n" +
+            "java_version=\"%s\" reference_url=\"%s\" command_line_args=\"%s\">\n" +
             "%s%s%s" +
             "</Result>";
     private static final String XML_BUILD_INFO =
@@ -170,7 +171,7 @@
 
         // Serialize to file
         ResultHandler.writeResults(SUITE_NAME, SUITE_VERSION, SUITE_PLAN, SUITE_BUILD,
-                result, resultDir, START_MS, END_MS, REFERENCE_URL);
+                result, resultDir, START_MS, END_MS, REFERENCE_URL, COMMAND_LINE_ARGS);
 
         // Parse the results and assert correctness
         checkResult(ResultHandler.getResults(resultsDir), resultDir);
@@ -211,7 +212,7 @@
             String output = String.format(XML_BASE, START_MS, END_MS, START_DISPLAY, END_DISPLAY,
                     SUITE_NAME, SUITE_VERSION, SUITE_PLAN, SUITE_BUILD, REPORT_VERSION, DEVICES,
                     hostName, OS_NAME, OS_VERSION, OS_ARCH, JAVA_VENDOR,
-                    JAVA_VERSION, REFERENCE_URL, buildInfo, summary, modules);
+                    JAVA_VERSION, REFERENCE_URL, COMMAND_LINE_ARGS, buildInfo, summary, modules);
             writer.write(output);
             writer.flush();