• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package com.android.tradefed.invoker.logger;
17 
18 import com.android.tradefed.invoker.ExecutionFiles;
19 import com.android.tradefed.invoker.IInvocationContext;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.result.ActionInProgress;
22 import com.android.tradefed.result.FailureDescription;
23 import com.android.tradefed.result.error.ErrorIdentifier;
24 
25 import java.io.File;
26 import java.lang.StackWalker.Option;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.concurrent.ConcurrentHashMap;
31 
32 import javax.annotation.Nullable;
33 
34 /**
35  * A class that tracks and provides the current invocation information useful anywhere inside the
36  * invocation.
37  */
38 public class CurrentInvocation {
39 
40     /** Some special named key that we will always populate for the invocation. */
41     public enum InvocationInfo {
42         WORK_FOLDER("work_folder");
43 
44         private final String mKeyName;
45 
InvocationInfo(String key)46         private InvocationInfo(String key) {
47             mKeyName = key;
48         }
49 
50         @Override
toString()51         public String toString() {
52             return mKeyName;
53         }
54     }
55 
56     /** Describes the level of isolation */
57     public enum IsolationGrade {
58         NOT_ISOLATED, // No action were taken to isolate the test.
59         REBOOT_ISOLATED, // Reboot was done before the test.
60         FULLY_ISOLATED; // Test received a fresh device.
61     }
62 
CurrentInvocation()63     private CurrentInvocation() {}
64 
65     /** Internal storage of the invocation values. */
66     private static class InternalInvocationTracking {
67         public Map<InvocationInfo, File> mInvocationInfoFiles = new HashMap<>();
68         public ExecutionFiles mExecutionFiles;
69         public ActionInProgress mActionInProgress = ActionInProgress.UNSET;
70         public IsolationGrade mIsModuleIsolated = IsolationGrade.FULLY_ISOLATED;
71         public IsolationGrade mIsRunIsolated = IsolationGrade.FULLY_ISOLATED;
72         public IInvocationContext mContext;
73         public IInvocationContext mModuleContext;
74     }
75 
76     /**
77      * Track info per ThreadGroup as a proxy to invocation since an invocation run within one
78      * threadgroup.
79      */
80     private static final Map<ThreadGroup, InternalInvocationTracking> mPerGroupInfo =
81             new ConcurrentHashMap<ThreadGroup, CurrentInvocation.InternalInvocationTracking>();
82 
83     private static final Map<ThreadGroup, Map<InvocationLocal<?>, Optional<?>>> mInvocationLocals =
84             new ConcurrentHashMap<>();
85 
86     private static ThreadLocal<ThreadGroup> sLocal = new ThreadLocal<>();
87 
88     /** Tracks a localized context when using the properties inside the gRPC server */
setLocalGroup(ThreadGroup tg)89     public static void setLocalGroup(ThreadGroup tg) {
90         sLocal.set(tg);
91     }
92 
93     /** Resets the localized context. */
resetLocalGroup()94     public static void resetLocalGroup() {
95         sLocal.remove();
96     }
97 
98     /**
99      * Add one key-value to be tracked at the invocation level.
100      *
101      * @param key The key under which the invocation info will be tracked.
102      * @param value The value of the invocation metric.
103      */
addInvocationInfo(InvocationInfo key, File value)104     public static void addInvocationInfo(InvocationInfo key, File value) {
105         ThreadGroup group = Thread.currentThread().getThreadGroup();
106         synchronized (mPerGroupInfo) {
107             if (mPerGroupInfo.get(group) == null) {
108                 mPerGroupInfo.put(group, new InternalInvocationTracking());
109             }
110             mPerGroupInfo.get(group).mInvocationInfoFiles.put(key, value);
111         }
112     }
113 
114     /** Returns the Map of invocation metrics for the invocation in progress. */
getInfo(InvocationInfo key)115     public static File getInfo(InvocationInfo key) {
116         ThreadGroup group = Thread.currentThread().getThreadGroup();
117         synchronized (mPerGroupInfo) {
118             if (sLocal.get() != null) {
119                 group = sLocal.get();
120             }
121             if (mPerGroupInfo.get(group) == null) {
122                 mPerGroupInfo.put(group, new InternalInvocationTracking());
123             }
124             return mPerGroupInfo.get(group).mInvocationInfoFiles.get(key);
125         }
126     }
127 
128     /** Returns the current work folder for the invocation or null if none set yet. */
getWorkFolder()129     public static File getWorkFolder() {
130         File workfolder = getInfo(InvocationInfo.WORK_FOLDER);
131         if (workfolder == null || !workfolder.exists()) {
132             return null;
133         }
134         return workfolder;
135     }
136 
137     /** Clear the invocation info for an invocation. */
clearInvocationInfos()138     public static void clearInvocationInfos() {
139         ThreadGroup group = Thread.currentThread().getThreadGroup();
140         synchronized (mPerGroupInfo) {
141             mPerGroupInfo.remove(group);
142         }
143         mInvocationLocals.remove(group);
144     }
145 
146     /**
147      * One-time registration of the {@link ExecutionFiles}. This is done by the Test Harness.
148      *
149      * @param invocFiles The registered {@link ExecutionFiles}.
150      */
registerExecutionFiles(ExecutionFiles invocFiles)151     public static void registerExecutionFiles(ExecutionFiles invocFiles) {
152         ThreadGroup group = Thread.currentThread().getThreadGroup();
153         synchronized (mPerGroupInfo) {
154             if (mPerGroupInfo.get(group) == null) {
155                 mPerGroupInfo.put(group, new InternalInvocationTracking());
156             }
157             if (mPerGroupInfo.get(group).mExecutionFiles == null) {
158                 mPerGroupInfo.get(group).mExecutionFiles = invocFiles;
159             } else {
160                 CLog.w(
161                         "CurrentInvocation#registerExecutionFiles should only be called once "
162                                 + "per invocation.");
163             }
164         }
165     }
166 
167     /** Returns the {@link ExecutionFiles} for the invocation. */
getInvocationFiles()168     public static ExecutionFiles getInvocationFiles() {
169         ThreadGroup group = Thread.currentThread().getThreadGroup();
170         synchronized (mPerGroupInfo) {
171             if (mPerGroupInfo.get(group) == null) {
172                 mPerGroupInfo.put(group, new InternalInvocationTracking());
173             }
174             return mPerGroupInfo.get(group).mExecutionFiles;
175         }
176     }
177 
178     /** Sets the {@link ActionInProgress} for the invocation. */
setActionInProgress(ActionInProgress action)179     public static void setActionInProgress(ActionInProgress action) {
180         ThreadGroup group = Thread.currentThread().getThreadGroup();
181         synchronized (mPerGroupInfo) {
182             if (mPerGroupInfo.get(group) == null) {
183                 mPerGroupInfo.put(group, new InternalInvocationTracking());
184             }
185             mPerGroupInfo.get(group).mActionInProgress = action;
186         }
187     }
188 
189     /** Returns the current {@link ActionInProgress} for the invocation. Can be null. */
getActionInProgress()190     public static @Nullable ActionInProgress getActionInProgress() {
191         ThreadGroup group = Thread.currentThread().getThreadGroup();
192         synchronized (mPerGroupInfo) {
193             if (mPerGroupInfo.get(group) == null) {
194                 return null;
195             }
196             return mPerGroupInfo.get(group).mActionInProgress;
197         }
198     }
199 
200     /** Sets the {@link IInvocationContext} for the invocation. */
setInvocationContext(IInvocationContext context)201     public static void setInvocationContext(IInvocationContext context) {
202         ThreadGroup group = Thread.currentThread().getThreadGroup();
203         synchronized (mPerGroupInfo) {
204             if (mPerGroupInfo.get(group) == null) {
205                 mPerGroupInfo.put(group, new InternalInvocationTracking());
206             }
207             mPerGroupInfo.get(group).mContext = context;
208         }
209     }
210 
211     /** Returns the current {@link IInvocationContext} for the invocation. Can be null. */
getInvocationContext()212     public static @Nullable IInvocationContext getInvocationContext() {
213         ThreadGroup group = Thread.currentThread().getThreadGroup();
214         synchronized (mPerGroupInfo) {
215             if (mPerGroupInfo.get(group) == null) {
216                 return null;
217             }
218             return mPerGroupInfo.get(group).mContext;
219         }
220     }
221 
222     /** Sets the module {@link IInvocationContext} of the currently running module. */
setModuleContext(IInvocationContext moduleContext)223     public static void setModuleContext(IInvocationContext moduleContext) {
224         ThreadGroup group = Thread.currentThread().getThreadGroup();
225         synchronized (mPerGroupInfo) {
226             if (mPerGroupInfo.get(group) == null) {
227                 mPerGroupInfo.put(group, new InternalInvocationTracking());
228             }
229             mPerGroupInfo.get(group).mModuleContext = moduleContext;
230         }
231     }
232 
233     /**
234      * Returns the module {@link IInvocationContext} for the current module. Can be null if out of
235      * scope of a module run.
236      */
getModuleContext()237     public static @Nullable IInvocationContext getModuleContext() {
238         ThreadGroup group = Thread.currentThread().getThreadGroup();
239         synchronized (mPerGroupInfo) {
240             if (mPerGroupInfo.get(group) == null) {
241                 return null;
242             }
243             return mPerGroupInfo.get(group).mModuleContext;
244         }
245     }
246 
247     /** Returns whether the current suite module executed was isolated or not. */
moduleCurrentIsolation()248     public static IsolationGrade moduleCurrentIsolation() {
249         ThreadGroup group = Thread.currentThread().getThreadGroup();
250         synchronized (mPerGroupInfo) {
251             if (mPerGroupInfo.get(group) == null) {
252                 return IsolationGrade.NOT_ISOLATED;
253             }
254             return mPerGroupInfo.get(group).mIsModuleIsolated;
255         }
256     }
257 
258     /** Update whether the suite module is isolated or not. */
setModuleIsolation(IsolationGrade isolation)259     public static void setModuleIsolation(IsolationGrade isolation) {
260         ThreadGroup group = Thread.currentThread().getThreadGroup();
261         synchronized (mPerGroupInfo) {
262             if (mPerGroupInfo.get(group) == null) {
263                 mPerGroupInfo.put(group, new InternalInvocationTracking());
264             }
265             mPerGroupInfo.get(group).mIsModuleIsolated = isolation;
266         }
267     }
268 
269     /** Returns whether the current test run executed was isolated or not. */
runCurrentIsolation()270     public static IsolationGrade runCurrentIsolation() {
271         ThreadGroup group = Thread.currentThread().getThreadGroup();
272         synchronized (mPerGroupInfo) {
273             if (mPerGroupInfo.get(group) == null) {
274                 return IsolationGrade.NOT_ISOLATED;
275             }
276             return mPerGroupInfo.get(group).mIsRunIsolated;
277         }
278     }
279 
280     /** Update whether the test run is isolated or not. */
setRunIsolation(IsolationGrade isolation)281     public static void setRunIsolation(IsolationGrade isolation) {
282         ThreadGroup group = Thread.currentThread().getThreadGroup();
283         synchronized (mPerGroupInfo) {
284             if (mPerGroupInfo.get(group) == null) {
285                 mPerGroupInfo.put(group, new InternalInvocationTracking());
286             }
287             mPerGroupInfo.get(group).mIsRunIsolated = isolation;
288         }
289     }
290 
291     /**
292      * Create a failure associated with the invocation action in progress. Convenience utility to
293      * avoid calling {@link FailureDescription#setActionInProgress(ActionInProgress)}.
294      */
createFailure( String errorMessage, ErrorIdentifier errorIdentifier)295     public static FailureDescription createFailure(
296             String errorMessage, ErrorIdentifier errorIdentifier) {
297         FailureDescription failure = FailureDescription.create(errorMessage);
298         ActionInProgress action = getActionInProgress();
299         if (action != null) {
300             failure.setActionInProgress(action);
301         }
302         if (errorIdentifier != null) {
303             failure.setErrorIdentifier(errorIdentifier);
304             failure.setFailureStatus(errorIdentifier.status());
305         }
306         // Automatically populate the origin
307         Class<?> clazz = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass();
308         failure.setOrigin(clazz.getCanonicalName());
309         return failure;
310     }
311 
getLocal(InvocationLocal<T> local)312     static <T> T getLocal(InvocationLocal<T> local) {
313         ThreadGroup group = Thread.currentThread().getThreadGroup();
314         Map<InvocationLocal<?>, Optional<?>> locals =
315                 mInvocationLocals.computeIfAbsent(group, unused -> new ConcurrentHashMap<>());
316 
317         // Note that ConcurrentHashMap guarantees that the function is atomic and called at-most
318         // once.
319         Optional<?> holder =
320                 locals.computeIfAbsent(local, unused -> Optional.ofNullable(local.initialValue()));
321 
322         @SuppressWarnings("unchecked")
323         T value = (T) holder.orElse(null);
324         return value;
325     }
326 }
327