1 /*
2  * Copyright (C) 2015 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.util;
18 
19 import com.android.ddmlib.IShellOutputReceiver;
20 import com.android.ddmlib.MultiLineReceiver;
21 import com.android.tradefed.log.LogUtil.CLog;
22 
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 /**
34  * A {@link IShellOutputReceiver} that parses the output of a 'pm list instrumentation' query
35  */
36 public class ListInstrumentationParser extends MultiLineReceiver {
37 
38     // Each line of output from `pm list instrumentation` has the following format:
39     // instrumentation:com.example.test/android.test.InstrumentationTestRunner (target=com.example)
40     private static final Pattern LIST_INSTR_PATTERN =
41             Pattern.compile("instrumentation:(.+)/(.+) \\(target=(.+)\\)");
42 
43     // A set of shardable instrumentation runner names. A limitation of the `pm list
44     // instrumentation` output is that Tradefed will be unable to tell whether or not an
45     // Instrumentation is shardable. A workaround is to have a list of shardable instrumentation
46     // runners, and check if a target uses that particular runner, although this means that any
47     // subclasses or other custom runner classes won't be acknowledged as shardable.
48     public static final Set<String> SHARDABLE_RUNNERS =
49             new HashSet<>(
50                     Arrays.asList(
51                             "android.support.test.runner.AndroidJUnitRunner",
52                             "androidx.test.runner.AndroidJUnitRunner"));
53 
54     private List<InstrumentationTarget> mInstrumentationTargets = new ArrayList<>();
55 
56     public static class InstrumentationTarget implements Comparable<InstrumentationTarget> {
57         public final String packageName;
58         public final String runnerName;
59         public final String targetName;
60 
InstrumentationTarget(String packageName, String runnerName, String targetName)61         public InstrumentationTarget(String packageName, String runnerName, String targetName) {
62             this.packageName = packageName;
63             this.runnerName = runnerName;
64             this.targetName = targetName;
65         }
66 
67         /**
68          * {@inheritDoc}
69          */
70         @Override
equals(Object object)71         public boolean equals(Object object) {
72             if (object instanceof InstrumentationTarget) {
73                 InstrumentationTarget that = (InstrumentationTarget)object;
74                 return Objects.equals(this.packageName, that.packageName)
75                         && Objects.equals(this.runnerName, that.runnerName)
76                         && Objects.equals(this.targetName, that.targetName);
77             }
78             return false;
79         }
80 
81         /**
82          * {@inheritDoc}
83          */
84         @Override
hashCode()85         public int hashCode() {
86             return Objects.hash(packageName, runnerName, targetName);
87         }
88 
89         /**
90          * {@inheritDoc}
91          */
92         @Override
compareTo(InstrumentationTarget o)93         public int compareTo(InstrumentationTarget o) {
94             if (!this.packageName.equals(o.packageName)) {
95                 return this.packageName.compareTo(o.packageName);
96             } else if (!this.runnerName.equals(o.runnerName)) {
97                 return this.runnerName.compareTo(o.runnerName);
98             }
99             return this.targetName.compareTo(o.targetName);
100         }
101 
102         /**
103          * {@inheritDoc}
104          */
105         @Override
toString()106         public String toString() {
107             return String.format("instrumentation:%s/%s (target=%s)",
108                     packageName, runnerName, targetName);
109         }
110 
111         /**
112          * Returns <tt>true</tt> if this instrumentation target is shardable.
113          */
isShardable()114         public boolean isShardable() {
115             return SHARDABLE_RUNNERS.contains(runnerName);
116         }
117     }
118 
getInstrumentationTargets()119     public List<InstrumentationTarget> getInstrumentationTargets() {
120         Collections.sort(mInstrumentationTargets);
121         return mInstrumentationTargets;
122     }
123 
124     /**
125      * {@inheritDoc}
126      */
127     @Override
isCancelled()128     public boolean isCancelled() {
129         return false;
130     }
131 
132     /**
133      * {@inheritDoc}
134      */
135     @Override
processNewLines(String[] lines)136     public void processNewLines(String[] lines) {
137         for (String line : lines) {
138             CLog.d(line);
139             Matcher m = LIST_INSTR_PATTERN.matcher(line);
140             if (m.find()) {
141                 mInstrumentationTargets.add(
142                         new InstrumentationTarget(m.group(1), m.group(2), m.group(3)));
143             }
144         }
145     }
146 }
147