• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.adservices.mockito;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.eq;
23 
24 import android.util.ArrayMap;
25 import android.util.Log;
26 
27 import com.android.adservices.shared.testing.LogEntry;
28 import com.android.adservices.shared.testing.Logger.LogLevel;
29 import com.android.internal.util.Preconditions;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.stream.Collectors;
37 
38 // TODO(b/306522832): move to shared project (device-side)
39 /** Class used to intercept static calls to {@link Log} so they can be verified. */
40 public final class LogInterceptor {
41 
42     private static final String TAG = LogInterceptor.class.getSimpleName();
43 
44     private Map<String, List<LogEntry>> mEntries = new ArrayMap<>();
45 
LogInterceptor()46     private LogInterceptor() {}
47 
48     /**
49      * Creates an interceptor for {@link Log} calls using the given {@code tag} and {@code levels}.
50      */
forTagAndLevels(String tag, LogLevel... levels)51     public static LogInterceptor forTagAndLevels(String tag, LogLevel... levels) {
52         // Log.v is used inside the Answers below, so it cannot be spied upon
53         Preconditions.checkArgument(
54                 !TAG.equals(Objects.requireNonNull(tag, "tag cannot be null")),
55                 "cannot intercept tag %s",
56                 TAG);
57 
58         LogInterceptor interceptor = new LogInterceptor();
59         for (LogLevel level : levels) {
60             switch (level) {
61                 case DEBUG:
62                     doAnswer(
63                                     invocation -> {
64                                         Log.d(TAG, invocation.toString());
65                                         interceptor.log(
66                                                 new LogEntry(
67                                                         LogLevel.DEBUG,
68                                                         invocation.getArgument(0),
69                                                         invocation.getArgument(1)));
70                                         invocation.callRealMethod();
71                                         return null;
72                                     })
73                             .when(() -> Log.d(eq(tag), any()));
74 
75                     doAnswer(
76                                     invocation -> {
77                                         Log.d(TAG, invocation.toString());
78                                         interceptor.log(
79                                                 new LogEntry(
80                                                         LogLevel.DEBUG,
81                                                         invocation.getArgument(0),
82                                                         invocation.getArgument(1),
83                                                         invocation.getArgument(2)));
84                                         invocation.callRealMethod();
85                                         return null;
86                                     })
87                             .when(() -> Log.d(eq(tag), any(), any()));
88                     break;
89                 case VERBOSE:
90                     doAnswer(
91                                     invocation -> {
92                                         Log.v(TAG, invocation.toString());
93                                         interceptor.log(
94                                                 new LogEntry(
95                                                         LogLevel.VERBOSE,
96                                                         invocation.getArgument(0),
97                                                         invocation.getArgument(1)));
98                                         invocation.callRealMethod();
99                                         return null;
100                                     })
101                             .when(() -> Log.v(eq(tag), any()));
102 
103                     doAnswer(
104                                     invocation -> {
105                                         Log.v(TAG, invocation.toString());
106                                         interceptor.log(
107                                                 new LogEntry(
108                                                         LogLevel.VERBOSE,
109                                                         invocation.getArgument(0),
110                                                         invocation.getArgument(1),
111                                                         invocation.getArgument(2)));
112                                         invocation.callRealMethod();
113                                         return null;
114                                     })
115                             .when(() -> Log.v(eq(tag), any(), any()));
116                     break;
117                 case ERROR:
118                     doAnswer(
119                                     invocation -> {
120                                         Log.v(TAG, invocation.toString());
121                                         interceptor.log(
122                                                 new LogEntry(
123                                                         LogLevel.ERROR,
124                                                         invocation.getArgument(0),
125                                                         invocation.getArgument(1)));
126                                         invocation.callRealMethod();
127                                         return null;
128                                     })
129                             .when(() -> Log.e(eq(tag), any()));
130 
131                     doAnswer(
132                                     invocation -> {
133                                         Log.v(TAG, invocation.toString());
134                                         interceptor.log(
135                                                 new LogEntry(
136                                                         LogLevel.ERROR,
137                                                         invocation.getArgument(0),
138                                                         invocation.getArgument(1),
139                                                         invocation.getArgument(2)));
140                                         invocation.callRealMethod();
141                                         return null;
142                                     })
143                             .when(() -> Log.e(eq(tag), any(), any()));
144                     break;
145                 default:
146                     // NOTE: current tests are only intercepting VERBOSE and ERROR; more levels
147                     // will be added on demand.
148                     throw new UnsupportedOperationException(
149                             "Not intercepting level " + level + " yet");
150             }
151         }
152 
153         return interceptor;
154     }
155 
156     /** Gets all calls that used that {@code tag}. */
getAllEntries(String tag)157     public List<LogEntry> getAllEntries(String tag) {
158         List<LogEntry> entries = mEntries.get(Objects.requireNonNull(tag, "tag cannot be null"));
159         return entries == null
160                 ? Collections.emptyList()
161                 : Collections.unmodifiableList(new ArrayList<>(entries));
162     }
163 
164     /**
165      * Gets the messages from all "plain" calls (i.e., whose argument was just the message, without
166      * a {@link Throwable}) to that {@code tag}.
167      */
getPlainMessages(String tag, LogLevel level)168     public List<String> getPlainMessages(String tag, LogLevel level) {
169         Objects.requireNonNull(tag, "tag cannot be null");
170         Objects.requireNonNull(level, "level cannot be null");
171         return getAllEntries(tag).stream()
172                 .filter(
173                         entry ->
174                                 entry.level.equals(level)
175                                         && entry.tag.equals(tag)
176                                         && entry.throwable == null)
177                 .map(entry -> entry.message)
178                 .collect(Collectors.toList());
179     }
180 
log(LogEntry logEntry)181     private void log(LogEntry logEntry) {
182         String tag = Objects.requireNonNull(logEntry, "logEntry cannot be null").tag;
183 
184         List<LogEntry> entries = mEntries.get(tag);
185         if (entries == null) {
186             entries = new ArrayList<>();
187             mEntries.put(tag, entries);
188         }
189         entries.add(logEntry);
190     }
191 }
192