1 /*
2  * Copyright (C) 2015 The Dagger Authors.
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 dagger.functional.producers.monitoring;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.fail;
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.Mockito.inOrder;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.verifyNoMoreInteractions;
25 import static org.mockito.Mockito.when;
26 
27 import com.google.common.base.Throwables;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.util.concurrent.ListenableFuture;
31 import com.google.common.util.concurrent.SettableFuture;
32 import dagger.functional.producers.ExecutorModule;
33 import dagger.producers.monitoring.ProducerMonitor;
34 import dagger.producers.monitoring.ProducerToken;
35 import dagger.producers.monitoring.ProductionComponentMonitor;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 import java.util.concurrent.ExecutionException;
39 import java.util.concurrent.Executors;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 import org.mockito.InOrder;
45 import org.mockito.Mock;
46 import org.mockito.MockitoAnnotations;
47 
48 /** Tests for production components using monitoring. */
49 @RunWith(JUnit4.class)
50 public final class MonitoringTest {
51   @Mock private ProductionComponentMonitor.Factory componentMonitorFactory;
52   @Mock private StringStub server1;
53   @Mock private StringStub server2;
54   private SettableFuture<String> server1Future;
55   private SettableFuture<String> server2Future;
56   private FakeProductionComponentMonitor componentMonitor;
57 
58   @Before
setUp()59   public void setUp() {
60     MockitoAnnotations.initMocks(this);
61     componentMonitor = new FakeProductionComponentMonitor();
62     when(componentMonitorFactory.create(any())).thenReturn(componentMonitor);
63     server1Future = SettableFuture.create();
64     server2Future = SettableFuture.create();
65     when(server1.run(any(String.class))).thenReturn(server1Future);
66     when(server2.run(any(String.class))).thenReturn(server2Future);
67   }
68 
69   @Test
basicMonitoring()70   public void basicMonitoring() throws Exception {
71     MonitoredComponent component =
72         DaggerMonitoredComponent.builder()
73             .monitoringModule(new MonitoringModule(componentMonitorFactory))
74             .stubModule(new StubModule(server1, server2))
75             .build();
76     ListenableFuture<String> output = component.output();
77     assertThat(componentMonitor.monitors).hasSize(3);
78     ImmutableList<Map.Entry<ProducerToken, ProducerMonitor>> entries =
79         ImmutableList.copyOf(componentMonitor.monitors.entrySet());
80     assertThat(entries.get(0).getKey().toString()).contains("CallServer2");
81     assertThat(entries.get(1).getKey().toString()).contains("CallServer1");
82     assertThat(entries.get(2).getKey().toString()).contains("RequestData");
83 
84     ProducerMonitor callServer2Monitor = entries.get(0).getValue();
85     ProducerMonitor callServer1Monitor = entries.get(1).getValue();
86     ProducerMonitor requestDataMonitor = entries.get(2).getValue();
87 
88     InOrder inOrder = inOrder(requestDataMonitor, callServer1Monitor, callServer2Monitor);
89     inOrder.verify(callServer2Monitor).requested();
90     inOrder.verify(callServer1Monitor).requested();
91     inOrder.verify(requestDataMonitor).requested();
92     inOrder.verify(requestDataMonitor).ready();
93     inOrder.verify(requestDataMonitor).methodStarting();
94     inOrder.verify(requestDataMonitor).methodFinished();
95     inOrder.verify(requestDataMonitor).succeeded("Hello, World!");
96     inOrder.verify(callServer1Monitor).ready();
97     inOrder.verify(callServer1Monitor).methodStarting();
98     inOrder.verify(callServer1Monitor).methodFinished();
99     verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor);
100 
101     server1Future.set("server 1 response");
102     inOrder.verify(callServer1Monitor).succeeded("server 1 response");
103     inOrder.verify(callServer2Monitor).ready();
104     inOrder.verify(callServer2Monitor).methodStarting();
105     inOrder.verify(callServer2Monitor).methodFinished();
106     verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor);
107 
108     server2Future.set("server 2 response");
109     inOrder.verify(callServer2Monitor).succeeded("server 2 response");
110     verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor);
111     assertThat(output.get()).isEqualTo("server 2 response");
112   }
113 
114   @Test
basicMonitoringWithFailure()115   public void basicMonitoringWithFailure() throws Exception {
116     MonitoredComponent component =
117         DaggerMonitoredComponent.builder()
118             .monitoringModule(new MonitoringModule(componentMonitorFactory))
119             .stubModule(new StubModule(server1, server2))
120             .build();
121     ListenableFuture<String> output = component.output();
122     assertThat(componentMonitor.monitors).hasSize(3);
123     ImmutableList<Map.Entry<ProducerToken, ProducerMonitor>> entries =
124         ImmutableList.copyOf(componentMonitor.monitors.entrySet());
125     assertThat(entries.get(0).getKey().toString()).contains("CallServer2");
126     assertThat(entries.get(1).getKey().toString()).contains("CallServer1");
127     assertThat(entries.get(2).getKey().toString()).contains("RequestData");
128 
129     ProducerMonitor callServer2Monitor = entries.get(0).getValue();
130     ProducerMonitor callServer1Monitor = entries.get(1).getValue();
131     ProducerMonitor requestDataMonitor = entries.get(2).getValue();
132 
133     InOrder inOrder = inOrder(requestDataMonitor, callServer1Monitor, callServer2Monitor);
134     inOrder.verify(callServer2Monitor).requested();
135     inOrder.verify(callServer1Monitor).requested();
136     inOrder.verify(requestDataMonitor).requested();
137     inOrder.verify(requestDataMonitor).ready();
138     inOrder.verify(requestDataMonitor).methodStarting();
139     inOrder.verify(requestDataMonitor).methodFinished();
140     inOrder.verify(requestDataMonitor).succeeded("Hello, World!");
141     inOrder.verify(callServer1Monitor).ready();
142     inOrder.verify(callServer1Monitor).methodStarting();
143     inOrder.verify(callServer1Monitor).methodFinished();
144     verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor);
145 
146     RuntimeException cause = new RuntimeException("monkey");
147     server1Future.setException(cause);
148     inOrder.verify(callServer1Monitor).failed(cause);
149     inOrder.verify(callServer2Monitor).ready();
150     inOrder.verify(callServer2Monitor).failed(any(Throwable.class));
151     verifyNoMoreInteractions(requestDataMonitor, callServer1Monitor, callServer2Monitor);
152     try {
153       output.get();
154       fail();
155     } catch (ExecutionException e) {
156       assertThat(Throwables.getRootCause(e)).isSameInstanceAs(cause);
157     }
158   }
159 
160   private static final class FakeProductionComponentMonitor extends ProductionComponentMonitor {
161     final Map<ProducerToken, ProducerMonitor> monitors = new LinkedHashMap<>();
162 
163     @Override
producerMonitorFor(ProducerToken token)164     public ProducerMonitor producerMonitorFor(ProducerToken token) {
165       ProducerMonitor monitor = mock(ProducerMonitor.class);
166       monitors.put(token, monitor);
167       return monitor;
168     }
169   }
170 
171   @Test
monitoringWithThreads()172   public void monitoringWithThreads() throws Exception {
173     ThreadRecordingProductionComponentMonitor componentMonitor =
174         new ThreadRecordingProductionComponentMonitor();
175     when(componentMonitorFactory.create(any())).thenReturn(componentMonitor);
176 
177     ThreadMonitoredComponent component =
178         DaggerThreadMonitoredComponent.builder()
179             .monitoringModule(new MonitoringModule(componentMonitorFactory))
180             .executorModule(new ExecutorModule(Executors.newFixedThreadPool(10)))
181             .build();
182     ThreadAccumulator threadAccumulator = component.threadAccumulator().get();
183 
184     assertThat(componentMonitor.monitors).hasSize(3);
185     ImmutableList<Map.Entry<ProducerToken, ThreadRecordingProducerMonitor>> entries =
186         ImmutableList.copyOf(componentMonitor.monitors.entrySet());
187 
188     assertThat(entries.get(0).getKey().toString()).contains("EntryPoint");
189     ThreadRecordingProducerMonitor entryPointMonitor = entries.get(0).getValue();
190     assertThat(entries.get(1).getKey().toString()).contains("Required");
191     ThreadRecordingProducerMonitor requiredMonitor = entries.get(1).getValue();
192     assertThat(entries.get(2).getKey().toString()).contains("Deferred");
193     ThreadRecordingProducerMonitor deferredMonitor = entries.get(2).getValue();
194 
195     // The entry point producer was requested from the main thread, then ran in its own thread.
196     assertThat(entryPointMonitor.requestedThreadId).isEqualTo(Thread.currentThread().getId());
197     assertThat(entryPointMonitor.startingThreadId)
198         .isEqualTo(threadAccumulator.threadId("entryPoint"));
199     assertThat(entryPointMonitor.finishedThreadId)
200         .isEqualTo(threadAccumulator.threadId("entryPoint"));
201 
202     // The deferred producer was requested by the required producer, then ran in its own thread.
203     assertThat(deferredMonitor.requestedThreadId).isEqualTo(threadAccumulator.threadId("required"));
204     assertThat(deferredMonitor.startingThreadId).isEqualTo(threadAccumulator.threadId("deferred"));
205     assertThat(deferredMonitor.finishedThreadId).isEqualTo(threadAccumulator.threadId("deferred"));
206 
207     // The required producer was requested by the entry point producer, then ran in its own thread.
208     assertThat(requiredMonitor.requestedThreadId).isEqualTo(entryPointMonitor.requestedThreadId);
209     assertThat(requiredMonitor.startingThreadId).isEqualTo(threadAccumulator.threadId("required"));
210     assertThat(requiredMonitor.finishedThreadId).isEqualTo(threadAccumulator.threadId("required"));
211 
212     // Each producer ran in a distinct thread.
213     ImmutableSet<Long> threadIds =
214         ImmutableSet.of(
215             Thread.currentThread().getId(),
216             threadAccumulator.threadId("required"),
217             threadAccumulator.threadId("deferred"),
218             threadAccumulator.threadId("entryPoint"));
219     assertThat(threadIds).hasSize(4);
220   }
221 
222   private static final class ThreadRecordingProductionComponentMonitor
223       extends ProductionComponentMonitor {
224     final Map<ProducerToken, ThreadRecordingProducerMonitor> monitors = new LinkedHashMap<>();
225 
226     @Override
producerMonitorFor(ProducerToken token)227     public ProducerMonitor producerMonitorFor(ProducerToken token) {
228       ThreadRecordingProducerMonitor monitor = new ThreadRecordingProducerMonitor();
229       monitors.put(token, monitor);
230       return monitor;
231     }
232   }
233 
234   private static final class ThreadRecordingProducerMonitor extends ProducerMonitor {
235     private long requestedThreadId;
236     private long startingThreadId;
237     private long finishedThreadId;
238 
239     @Override
requested()240     public void requested() {
241       requestedThreadId = Thread.currentThread().getId();
242     }
243 
244     @Override
methodStarting()245     public void methodStarting() {
246       startingThreadId = Thread.currentThread().getId();
247     }
248 
249     @Override
methodFinished()250     public void methodFinished() {
251       finishedThreadId = Thread.currentThread().getId();
252     }
253   }
254 }
255