• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.observatory;
18 
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.ArgsOptionParser;
21 import com.android.tradefed.config.ConfigurationException;
22 import com.android.tradefed.config.IConfiguration;
23 import com.android.tradefed.config.filter.GlobalTestFilter;
24 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
25 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
26 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
27 import com.android.tradefed.log.ITestLogger;
28 import com.android.tradefed.log.LogUtil.CLog;
29 import com.android.tradefed.result.FileInputStreamSource;
30 import com.android.tradefed.result.LogDataType;
31 import com.android.tradefed.result.error.InfraErrorIdentifier;
32 import com.android.tradefed.util.CommandResult;
33 import com.android.tradefed.util.CommandStatus;
34 import com.android.tradefed.util.FileUtil;
35 import com.android.tradefed.util.IRunUtil;
36 import com.android.tradefed.util.QuotationAwareTokenizer;
37 import com.android.tradefed.util.RunUtil;
38 import com.android.tradefed.util.StringEscapeUtils;
39 import com.android.tradefed.util.SystemUtil;
40 import com.android.tradefed.util.testmapping.TestMapping;
41 
42 import com.google.common.annotations.VisibleForTesting;
43 import com.google.common.base.Joiner;
44 import com.google.common.base.Strings;
45 
46 import org.json.JSONArray;
47 import org.json.JSONException;
48 import org.json.JSONObject;
49 
50 import java.io.File;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Map;
59 
60 /**
61  * A class for test launcher to call the TradeFed jar that packaged in the test suite to discover
62  * test modules.
63  *
64  * <p>TestDiscoveryInvoker will take {@link IConfiguration} and the test root directory from the
65  * launch control provider to make the launch control provider to invoke the workflow to use the
66  * config to query the packaged TradeFed jar file in the test suite root directory to retrieve test
67  * module names.
68  */
69 public class TestDiscoveryInvoker {
70 
71     private final IConfiguration mConfiguration;
72     private final String mDefaultConfigName;
73     private final File mRootDir;
74     private final IRunUtil mRunUtil = new RunUtil();
75     private final boolean mHasConfigFallback;
76     private final boolean mUseCurrentTradefed;
77     private File mTestDir;
78     private File mTestMappingZip;
79     private IBuildInfo mBuildInfo;
80     private ITestLogger mLogger;
81 
82     private static final TestDiscoveryUtil mTestDiscoveryUtil = new TestDiscoveryUtil();
83 
84     public static final String TRADEFED_OBSERVATORY_ENTRY_PATH =
85             TestDiscoveryExecutor.class.getName();
86     public static final String TEST_DEPENDENCIES_LIST_KEY = "TestDependencies";
87     public static final String TEST_MODULES_LIST_KEY = "TestModules";
88     public static final String TEST_ZIP_REGEXES_LIST_KEY = "TestZipRegexes";
89 
90     public static final String TEST_DISCOVERY_COMMENT_KEY = "TestDiscoveryComment";
91     public static final String PARTIAL_FALLBACK_KEY = "PartialFallback";
92     public static final String NO_POSSIBLE_TEST_DISCOVERY_KEY = "NoPossibleTestDiscovery";
93     public static final String TEST_MAPPING_ZIP_FILE = "TF_TEST_MAPPING_ZIP_FILE";
94     public static final String ROOT_DIRECTORY_ENV_VARIABLE_KEY =
95             "ROOT_TEST_DISCOVERY_USE_TEST_DIRECTORY";
96 
97     public static final String OUTPUT_FILE = "DISCOVERY_OUTPUT_FILE";
98     public static final String DISCOVERY_TRACE_FILE = "DISCOVERY_TRACE_FILE";
99     public static final String BWYN_DISCOVER_TEST_ZIP = "BWYN_DISCOVER_TEST_ZIP";
100 
101     private static final long DISCOVERY_TIMEOUT_MS = 180000L;
102 
103     @VisibleForTesting
getRunUtil()104     IRunUtil getRunUtil() {
105         return mRunUtil;
106     }
107 
108     @VisibleForTesting
getJava()109     String getJava() {
110         return SystemUtil.getRunningJavaBinaryPath().getAbsolutePath();
111     }
112 
113     @VisibleForTesting
createOutputFile()114     File createOutputFile() throws IOException {
115         if (mTestDiscoveryUtil.hasOutputResultFile()) {
116             File presetOutputFile = new File(System.getenv(TestDiscoveryInvoker.OUTPUT_FILE));
117             return presetOutputFile;
118         }
119         return FileUtil.createTempFile("discovery-output", ".txt");
120     }
121 
122     @VisibleForTesting
createTraceFile()123     File createTraceFile() throws IOException {
124         return FileUtil.createTempFile("discovery-trace", ".txt");
125     }
126 
getTestDir()127     public File getTestDir() {
128         return mTestDir;
129     }
130 
setTestDir(File testDir)131     public void setTestDir(File testDir) {
132         mTestDir = testDir;
133     }
134 
setTestMappingZip(File testMappingZip)135     public void setTestMappingZip(File testMappingZip) {
136         mTestMappingZip = testMappingZip;
137     }
138 
setBuildInfo(IBuildInfo buildInfo)139     public void setBuildInfo(IBuildInfo buildInfo) {
140         mBuildInfo = buildInfo;
141     }
142 
setTestLogger(ITestLogger logger)143     public void setTestLogger(ITestLogger logger) {
144         mLogger = logger;
145     }
146 
147     /** Creates an {@link TestDiscoveryInvoker} with a {@link IConfiguration} and root directory. */
TestDiscoveryInvoker(IConfiguration config, File rootDir)148     public TestDiscoveryInvoker(IConfiguration config, File rootDir) {
149         this(config, null, rootDir);
150     }
151 
152     /**
153      * Creates an {@link TestDiscoveryInvoker} with a {@link IConfiguration}, test launcher's
154      * default config name and root directory.
155      */
TestDiscoveryInvoker(IConfiguration config, String defaultConfigName, File rootDir)156     public TestDiscoveryInvoker(IConfiguration config, String defaultConfigName, File rootDir) {
157         this(config, defaultConfigName, rootDir, false, false);
158     }
159 
160     /**
161      * Creates an {@link TestDiscoveryInvoker} with a {@link IConfiguration}, test launcher's
162      * default config name, root directory and if fallback is required.
163      */
TestDiscoveryInvoker( IConfiguration config, String defaultConfigName, File rootDir, boolean hasConfigFallback, boolean useCurrentTradefed)164     public TestDiscoveryInvoker(
165             IConfiguration config,
166             String defaultConfigName,
167             File rootDir,
168             boolean hasConfigFallback,
169             boolean useCurrentTradefed) {
170         mConfiguration = config;
171         mDefaultConfigName = defaultConfigName;
172         mRootDir = rootDir;
173         mTestDir = null;
174         mHasConfigFallback = hasConfigFallback;
175         mUseCurrentTradefed = useCurrentTradefed;
176     }
177 
178     /**
179      * Retrieve a map of xTS test dependency names - categorized by either test modules or other
180      * test dependencies.
181      *
182      * @return A map of test dependencies which grouped by TEST_MODULES_LIST_KEY and
183      *     TEST_DEPENDENCIES_LIST_KEY.
184      * @throws IOException
185      * @throws JSONException
186      * @throws ConfigurationException
187      * @throws TestDiscoveryException
188      */
discoverTestDependencies()189     public Map<String, List<String>> discoverTestDependencies()
190             throws IOException, JSONException, ConfigurationException, TestDiscoveryException {
191         File outputFile = createOutputFile();
192         File traceFile = createTraceFile();
193         try (CloseableTraceScope ignored = new CloseableTraceScope("discoverTestDependencies")) {
194             Map<String, List<String>> dependencies = new HashMap<>();
195             // Build the classpath base on test root directory which should contain all the jars
196             String classPath = buildXtsClasspath(mRootDir);
197             // Build command line args to query the tradefed.jar in the root directory
198             List<String> args = buildJavaCmdForXtsDiscovery(classPath);
199             String[] subprocessArgs = args.toArray(new String[args.size()]);
200 
201             if (mHasConfigFallback) {
202                 getRunUtil()
203                         .setEnvVariable(
204                                 ROOT_DIRECTORY_ENV_VARIABLE_KEY, mRootDir.getAbsolutePath());
205             }
206             getRunUtil().setEnvVariable(OUTPUT_FILE, outputFile.getAbsolutePath());
207             getRunUtil().setEnvVariable(DISCOVERY_TRACE_FILE, traceFile.getAbsolutePath());
208             CommandResult res = getRunUtil().runTimedCmd(DISCOVERY_TIMEOUT_MS, subprocessArgs);
209             String stdout = res.getStdout();
210             CLog.i(String.format("Tradefed Observatory returned in stdout: %s", stdout));
211             if (res.getExitCode() != 0 || !res.getStatus().equals(CommandStatus.SUCCESS)) {
212                 DiscoveryExitCode exitCode = null;
213                 if (res.getExitCode() != null) {
214                     for (DiscoveryExitCode code : DiscoveryExitCode.values()) {
215                         if (code.exitCode() == res.getExitCode()) {
216                             exitCode = code;
217                         }
218                     }
219                 }
220                 if (DiscoveryExitCode.CONFIGURATION_EXCEPTION.equals(exitCode)) {
221                     throw new ConfigurationException(
222                             res.getStderr(), InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
223                 }
224                 throw new TestDiscoveryException(
225                         String.format(
226                                 "Tradefed observatory error, unable to discover test module names."
227                                         + " command used: %s error: %s",
228                                 Joiner.on(" ").join(subprocessArgs), res.getStderr()),
229                         null,
230                         exitCode);
231             }
232             try (CloseableTraceScope discoResults =
233                     new CloseableTraceScope("parse_discovery_results")) {
234 
235                 String result = FileUtil.readStringFromFile(outputFile);
236                 CLog.i("output file content: %s", result);
237 
238                 // For backward compatibility
239                 try {
240                     new JSONObject(result);
241                 } catch (JSONException e) {
242                     CLog.w("Output file was incorrect. Try falling back stdout");
243                     result = stdout;
244                 }
245 
246                 boolean noDiscovery = hasNoPossibleDiscovery(result);
247                 if (noDiscovery) {
248                     dependencies.put(NO_POSSIBLE_TEST_DISCOVERY_KEY, Arrays.asList("true"));
249                 }
250                 List<String> testModules = parseTestDiscoveryOutput(result, TEST_MODULES_LIST_KEY);
251                 if (!noDiscovery) {
252                     InvocationMetricLogger.addInvocationMetrics(
253                             InvocationMetricKey.TEST_DISCOVERY_MODULE_COUNT, testModules.size());
254                 }
255                 if (!testModules.isEmpty()) {
256                     dependencies.put(TEST_MODULES_LIST_KEY, testModules);
257                 } else {
258                     // Only report no finding if discovery actually took effect
259                     if (!noDiscovery) {
260                         mConfiguration.getSkipManager().reportDiscoveryWithNoTests();
261                     }
262                 }
263 
264                 List<String> testDependencies =
265                         parseTestDiscoveryOutput(result, TEST_DEPENDENCIES_LIST_KEY);
266                 if (!testDependencies.isEmpty()) {
267                     dependencies.put(TEST_DEPENDENCIES_LIST_KEY, testDependencies);
268                 }
269 
270                 String partialFallback = parsePartialFallback(result);
271                 if (partialFallback != null) {
272                     dependencies.put(PARTIAL_FALLBACK_KEY, Arrays.asList(partialFallback));
273                 }
274                 if (!noDiscovery) {
275                     mConfiguration
276                             .getSkipManager()
277                             .reportDiscoveryDependencies(testModules, testDependencies);
278                 }
279                 return dependencies;
280             }
281         } finally {
282             // Have output file set means other source is anticipating to read the output file.
283             // Therefore avoid deleting the output file if its set. (e.g. BWYN script)
284             if (!mTestDiscoveryUtil.hasOutputResultFile()) {
285                 FileUtil.deleteFile(outputFile);
286             }
287             try (FileInputStreamSource source = new FileInputStreamSource(traceFile, true)) {
288                 if (mLogger != null) {
289                     mLogger.testLog("discovery-trace", LogDataType.PERFETTO, source);
290                 }
291             }
292         }
293     }
294 
295     /**
296      * Retrieve a map of test mapping test module names.
297      *
298      * @return A map of test module names which grouped by TEST_MODULES_LIST_KEY.
299      * @throws IOException
300      * @throws JSONException
301      * @throws ConfigurationException
302      * @throws TestDiscoveryException
303      */
discoverTestMappingDependencies()304     public Map<String, List<String>> discoverTestMappingDependencies()
305             throws IOException, JSONException, ConfigurationException, TestDiscoveryException {
306         File outputFile = createOutputFile();
307         File traceFile = createTraceFile();
308         try (CloseableTraceScope ignored =
309                 new CloseableTraceScope("discoverTestMappingDependencies")) {
310             List<String> fullCommandLineArgs =
311                     new ArrayList<String>(
312                             Arrays.asList(
313                                     QuotationAwareTokenizer.tokenizeLine(
314                                             mConfiguration.getCommandLine())));
315             // first arg is config name
316             fullCommandLineArgs.remove(0);
317             final ConfigurationTestMappingParserSettings mappingParserSettings =
318                     new ConfigurationTestMappingParserSettings();
319             ArgsOptionParser mappingOptionParser = new ArgsOptionParser(mappingParserSettings);
320             // Parse to collect all values of --cts-params as well config name
321             mappingOptionParser.parseBestEffort(fullCommandLineArgs, true);
322 
323             Map<String, List<String>> dependencies = new HashMap<>();
324             // Build the classpath base on the working directory
325             String classPath = buildTestMappingClasspath(mRootDir);
326             // Build command line args to query the tradefed.jar in the working directory
327             List<String> args = buildJavaCmdForTestMappingDiscovery(classPath);
328             String[] subprocessArgs = args.toArray(new String[args.size()]);
329 
330             // Pass the test mapping zip path to subprocess by environment variable
331             if (mTestMappingZip != null) {
332                 getRunUtil()
333                         .setEnvVariable(TEST_MAPPING_ZIP_FILE, mTestMappingZip.getAbsolutePath());
334             }
335             if (mBuildInfo != null) {
336                 if (mBuildInfo.getFile(TestMapping.TEST_MAPPINGS_ZIP) != null) {
337                     getRunUtil()
338                             .setEnvVariable(
339                                     TEST_MAPPING_ZIP_FILE,
340                                     mBuildInfo
341                                             .getFile(TestMapping.TEST_MAPPINGS_ZIP)
342                                             .getAbsolutePath());
343                     getRunUtil()
344                             .setEnvVariable(
345                                     TestMapping.TEST_MAPPINGS_ZIP,
346                                     mBuildInfo
347                                             .getFile(TestMapping.TEST_MAPPINGS_ZIP)
348                                             .getAbsolutePath());
349                 }
350                 for (String allowedList : mappingParserSettings.mAllowedTestLists) {
351                     if (mBuildInfo.getFile(allowedList) != null) {
352                         getRunUtil()
353                                 .setEnvVariable(
354                                         allowedList,
355                                         mBuildInfo.getFile(allowedList).getAbsolutePath());
356                     }
357                 }
358             }
359             if (!Strings.isNullOrEmpty(mappingParserSettings.mRunTestSuite)) {
360                 throw new TestDiscoveryException(
361                         String.format(
362                                 "Test discovery for test suite is not supported yet. Test suite:"
363                                         + " %s",
364                                 mappingParserSettings.mRunTestSuite),
365                         null);
366             }
367 
368             if (mHasConfigFallback) {
369                 getRunUtil()
370                         .setEnvVariable(
371                                 ROOT_DIRECTORY_ENV_VARIABLE_KEY, mRootDir.getAbsolutePath());
372             }
373             getRunUtil().setEnvVariable(DISCOVERY_TRACE_FILE, traceFile.getAbsolutePath());
374             getRunUtil().setEnvVariable(OUTPUT_FILE, outputFile.getAbsolutePath());
375             CommandResult res = getRunUtil().runTimedCmd(DISCOVERY_TIMEOUT_MS, subprocessArgs);
376             String stdout = res.getStdout();
377             CLog.i(String.format("Tradefed Observatory returned in stdout:\n %s", stdout));
378             if (res.getExitCode() != 0 || !res.getStatus().equals(CommandStatus.SUCCESS)) {
379                 throw new TestDiscoveryException(
380                         String.format(
381                                 "Tradefed observatory error, unable to discover test module names."
382                                         + " command used: %s error: %s",
383                                 Joiner.on(" ").join(subprocessArgs), res.getStderr()),
384                         null);
385             }
386             try (CloseableTraceScope discoResults =
387                     new CloseableTraceScope("parse_discovery_results")) {
388                 String result = FileUtil.readStringFromFile(outputFile);
389 
390                 boolean noDiscovery = hasNoPossibleDiscovery(result);
391                 if (noDiscovery) {
392                     dependencies.put(NO_POSSIBLE_TEST_DISCOVERY_KEY, Arrays.asList("true"));
393                 }
394                 List<String> testModules = parseTestDiscoveryOutput(result, TEST_MODULES_LIST_KEY);
395                 if (!noDiscovery) {
396                     InvocationMetricLogger.addInvocationMetrics(
397                             InvocationMetricKey.TEST_DISCOVERY_MODULE_COUNT, testModules.size());
398                 }
399                 if (!testModules.isEmpty()) {
400                     dependencies.put(TEST_MODULES_LIST_KEY, testModules);
401                 } else {
402                     if (!noDiscovery) {
403                         mConfiguration.getSkipManager().reportDiscoveryWithNoTests();
404                     }
405                 }
406                 String partialFallback = parsePartialFallback(result);
407                 if (partialFallback != null) {
408                     dependencies.put(PARTIAL_FALLBACK_KEY, Arrays.asList(partialFallback));
409                 }
410                 if (!noDiscovery) {
411                     if (!noDiscovery) {
412                         mConfiguration
413                                 .getSkipManager()
414                                 .reportDiscoveryDependencies(testModules, new ArrayList<String>());
415                     }
416                 }
417                 return dependencies;
418             }
419         } finally {
420             // Have output file set means other source is anticipating to read the output file.
421             // Therefore avoid deleting the output file if its set. (e.g. BWYN script)
422             if (!mTestDiscoveryUtil.hasOutputResultFile()) {
423                 FileUtil.deleteFile(outputFile);
424             }
425             try (FileInputStreamSource source = new FileInputStreamSource(traceFile, true)) {
426                 if (mLogger != null) {
427                     mLogger.testLog("discovery-trace", LogDataType.PERFETTO, source);
428                 }
429             }
430         }
431     }
432 
433     /**
434      * Build java cmd for invoking a subprocess to discover test mapping test module names.
435      *
436      * @return A list of java command args.
437      */
buildJavaCmdForTestMappingDiscovery(String classpath)438     private List<String> buildJavaCmdForTestMappingDiscovery(String classpath) {
439         List<String> fullCommandLineArgs =
440                 new ArrayList<String>(
441                         Arrays.asList(
442                                 QuotationAwareTokenizer.tokenizeLine(
443                                         mConfiguration.getCommandLine())));
444 
445         List<String> args = new ArrayList<>();
446 
447         args.add(getJava());
448 
449         args.add("-cp");
450         args.add(classpath);
451 
452         args.add(TRADEFED_OBSERVATORY_ENTRY_PATH);
453 
454         // Delete invocation data from args which test discovery don't need
455         int i = 0;
456         while (i < fullCommandLineArgs.size()) {
457             if (fullCommandLineArgs.get(i).equals("--invocation-data")) {
458                 i = i + 2;
459             } else {
460                 args.add(fullCommandLineArgs.get(i));
461                 i = i + 1;
462             }
463         }
464         return args;
465     }
466 
467     /**
468      * Build java cmd for invoking a subprocess to discover XTS test module names.
469      *
470      * @return A list of java command args.
471      * @throws ConfigurationException
472      */
buildJavaCmdForXtsDiscovery(String classpath)473     private List<String> buildJavaCmdForXtsDiscovery(String classpath)
474             throws ConfigurationException, TestDiscoveryException {
475         List<String> fullCommandLineArgs =
476                 new ArrayList<String>(
477                         Arrays.asList(
478                                 QuotationAwareTokenizer.tokenizeLine(
479                                         mConfiguration.getCommandLine())));
480         // first arg is config name
481         fullCommandLineArgs.remove(0);
482 
483         final ConfigurationCtsParserSettings ctsParserSettings =
484                 new ConfigurationCtsParserSettings();
485         ArgsOptionParser ctsOptionParser = new ArgsOptionParser(ctsParserSettings);
486 
487         // Parse to collect all values of --cts-params as well config name
488         ctsOptionParser.parseBestEffort(fullCommandLineArgs, true);
489 
490         List<String> ctsParams = ctsParserSettings.mCtsParams;
491         for (String globalFilter : ctsParserSettings.mStrictIncludeFilters) {
492             ctsParams.add("--" + GlobalTestFilter.STRICT_INCLUDE_FILTER_OPTION);
493             ctsParams.add(globalFilter);
494         }
495         String configName = ctsParserSettings.mConfigName;
496 
497         if (configName == null) {
498             if (mDefaultConfigName == null) {
499                 throw new TestDiscoveryException(
500                         String.format(
501                                 "Failed to extract config-name from parent test command options,"
502                                         + " unable to build args to invoke tradefed observatory."
503                                         + " Parent test command options is: %s",
504                                 fullCommandLineArgs),
505                         null,
506                         DiscoveryExitCode.ERROR);
507             } else {
508                 CLog.i(
509                         String.format(
510                                 "No config name provided in the command args, use default config"
511                                         + " name %s",
512                                 mDefaultConfigName));
513                 configName = mDefaultConfigName;
514             }
515         }
516         List<String> args = new ArrayList<>();
517         args.add(getJava());
518 
519         args.add("-cp");
520         args.add(classpath);
521 
522         // Cts V2 requires CTS_ROOT to be set or VTS_ROOT for vts run
523         args.add(
524                 String.format(
525                         "-D%s=%s", ctsParserSettings.mRootdirVar, mRootDir.getAbsolutePath()));
526 
527         args.add(TRADEFED_OBSERVATORY_ENTRY_PATH);
528         args.add(configName);
529 
530         // Tokenize args to be passed to CtsTest/XtsTest
531         args.addAll(StringEscapeUtils.paramsToArgs(ctsParams));
532 
533         return args;
534     }
535 
536     /**
537      * Build the classpath string based on jars in the sandbox's working directory.
538      *
539      * @return A string of classpaths.
540      * @throws IOException
541      */
buildTestMappingClasspath(File workingDir)542     private String buildTestMappingClasspath(File workingDir) throws IOException {
543         try (CloseableTraceScope ignored = new CloseableTraceScope("build_classpath")) {
544             List<String> classpathList = new ArrayList<>();
545 
546             if (!workingDir.exists()) {
547                 throw new FileNotFoundException("Couldn't find the build directory");
548             }
549 
550             if (workingDir.listFiles().length == 0) {
551                 throw new FileNotFoundException(
552                         String.format(
553                                 "Could not find any files under %s", workingDir.getAbsolutePath()));
554             }
555             for (File toolsFile : workingDir.listFiles()) {
556                 if (toolsFile.getName().endsWith(".jar")) {
557                     classpathList.add(toolsFile.getAbsolutePath());
558                 }
559             }
560             Collections.sort(classpathList);
561             if (mUseCurrentTradefed) {
562                 classpathList.add(getCurrentClassPath());
563             }
564 
565             return Joiner.on(":").join(classpathList);
566         }
567     }
568 
getCurrentClassPath()569     private String getCurrentClassPath() {
570         return System.getProperty("java.class.path");
571     }
572 
573     /**
574      * Build the classpath string based on jars in the XTS test root directory's tools folder.
575      *
576      * @return A string of classpaths.
577      * @throws IOException
578      */
buildXtsClasspath(File ctsRoot)579     private String buildXtsClasspath(File ctsRoot) throws IOException {
580         List<File> classpathList = new ArrayList<>();
581 
582         if (!ctsRoot.exists()) {
583             throw new FileNotFoundException("Couldn't find the build directory: " + ctsRoot);
584         }
585 
586         // Safe to assume single dir from extracted zip
587         if (ctsRoot.list().length != 1) {
588             throw new RuntimeException(
589                     "List of sub directory does not contain only one item "
590                             + "current list is:"
591                             + Arrays.toString(ctsRoot.list()));
592         }
593         String mainDirName = ctsRoot.list()[0];
594         // Jar files from the downloaded cts/xts
595         File jarCtsPath = new File(new File(ctsRoot, mainDirName), "tools");
596         if (jarCtsPath.listFiles().length == 0) {
597             throw new FileNotFoundException(
598                     String.format(
599                             "Could not find any files under %s", jarCtsPath.getAbsolutePath()));
600         }
601         for (File toolsFile : jarCtsPath.listFiles()) {
602             if (toolsFile.getName().endsWith(".jar")) {
603                 classpathList.add(toolsFile);
604             }
605         }
606         Collections.sort(classpathList);
607 
608         return Joiner.on(":").join(classpathList);
609     }
610 
611     /**
612      * Parse test module names from the tradefed observatory's output JSON string.
613      *
614      * @param discoveryOutput JSON string from test discovery
615      * @param dependencyListKey test dependency type
616      * @return A list of test module names.
617      * @throws JSONException
618      */
parseTestDiscoveryOutput(String discoveryOutput, String dependencyListKey)619     private List<String> parseTestDiscoveryOutput(String discoveryOutput, String dependencyListKey)
620             throws JSONException {
621         JSONObject jsonObject = new JSONObject(discoveryOutput);
622         List<String> testModules = new ArrayList<>();
623         if (jsonObject.has(dependencyListKey)) {
624             JSONArray jsonArray = jsonObject.getJSONArray(dependencyListKey);
625             for (int i = 0; i < jsonArray.length(); i++) {
626                 testModules.add(jsonArray.getString(i));
627             }
628         }
629         return testModules;
630     }
631 
parsePartialFallback(String discoveryOutput)632     private String parsePartialFallback(String discoveryOutput) throws JSONException {
633         JSONObject jsonObject = new JSONObject(discoveryOutput);
634         if (jsonObject.has(PARTIAL_FALLBACK_KEY)) {
635             return jsonObject.getString(PARTIAL_FALLBACK_KEY);
636         }
637         return null;
638     }
639 
hasNoPossibleDiscovery(String discoveryOutput)640     private boolean hasNoPossibleDiscovery(String discoveryOutput) throws JSONException {
641         JSONObject jsonObject = new JSONObject(discoveryOutput);
642         if (jsonObject.has(NO_POSSIBLE_TEST_DISCOVERY_KEY)) {
643             return true;
644         }
645         return false;
646     }
647 }
648