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.Mockito.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