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