|
74 | 74 | import static org.hamcrest.Matchers.hasEntry; |
75 | 75 | import static org.mockito.ArgumentMatchers.any; |
76 | 76 | import static org.mockito.ArgumentMatchers.argThat; |
| 77 | +import static org.mockito.ArgumentMatchers.contains; |
77 | 78 | import static org.mockito.ArgumentMatchers.eq; |
78 | 79 | import static org.mockito.Mockito.doAnswer; |
| 80 | +import static org.mockito.Mockito.doNothing; |
79 | 81 | import static org.mockito.Mockito.mock; |
80 | 82 | import static org.mockito.Mockito.spy; |
81 | 83 | import static org.mockito.Mockito.times; |
@@ -288,63 +290,54 @@ public void testProcessFileChanges() throws Exception { |
288 | 290 | verifyNoMoreInteractions(healthIndicatorService); |
289 | 291 | } |
290 | 292 |
|
291 | | - @SuppressWarnings("unchecked") |
292 | 293 | public void testInvalidJSON() throws Exception { |
293 | | - doAnswer((Answer<Void>) invocation -> { |
294 | | - invocation.getArgument(1, XContentParser.class).map(); // Throw if JSON is invalid |
295 | | - ((Consumer<Exception>) invocation.getArgument(3)).accept(null); |
296 | | - return null; |
297 | | - }).when(controller).process(any(), any(XContentParser.class), any(), any()); |
298 | | - |
299 | | - CyclicBarrier fileChangeBarrier = new CyclicBarrier(2); |
300 | | - fileSettingsService.addFileChangedListener(() -> awaitOrBust(fileChangeBarrier)); |
| 294 | + // Chop off the functionality so we don't run too much of the actual cluster logic that we're not testing |
| 295 | + doNothing().when(controller).updateErrorState(any()); |
| 296 | + doAnswer( |
| 297 | + (Answer<Void>) invocation -> { throw new AssertionError("Parse error should happen before this process method is called"); } |
| 298 | + ).when(controller).process(any(), any(ReservedStateChunk.class), any(), any()); |
301 | 299 |
|
| 300 | + // Don't really care about the initial state |
302 | 301 | Files.createDirectories(fileSettingsService.watchedFileDir()); |
303 | | - // contents of the JSON don't matter, we just need a file to exist |
304 | | - writeTestFile(fileSettingsService.watchedFile(), "{}"); |
| 302 | + doNothing().when(fileSettingsService).processInitialFileMissing(); |
| 303 | + fileSettingsService.start(); |
| 304 | + fileSettingsService.clusterChanged(new ClusterChangedEvent("test", clusterService.state(), ClusterState.EMPTY_STATE)); |
305 | 305 |
|
| 306 | + // Now break the JSON and wait |
| 307 | + CyclicBarrier fileChangeBarrier = new CyclicBarrier(2); |
306 | 308 | doAnswer((Answer<?>) invocation -> { |
307 | | - boolean returnedNormally = false; |
308 | 309 | try { |
309 | | - var result = invocation.callRealMethod(); |
310 | | - returnedNormally = true; |
311 | | - return result; |
312 | | - } catch (XContentParseException e) { |
313 | | - // We're expecting a parse error. processFileChanges specifies that this is supposed to throw ExecutionException. |
314 | | - throw new ExecutionException(e); |
315 | | - } catch (Throwable e) { |
316 | | - throw new AssertionError("Unexpected exception", e); |
| 310 | + return invocation.callRealMethod(); |
317 | 311 | } finally { |
318 | | - if (returnedNormally == false) { |
319 | | - // Because of the exception, listeners aren't notified, so we need to activate the barrier ourselves |
320 | | - awaitOrBust(fileChangeBarrier); |
321 | | - } |
| 312 | + awaitOrBust(fileChangeBarrier); |
322 | 313 | } |
323 | 314 | }).when(fileSettingsService).processFileChanges(); |
324 | | - |
325 | | - // Establish the initial valid JSON |
326 | | - fileSettingsService.start(); |
327 | | - fileSettingsService.clusterChanged(new ClusterChangedEvent("test", clusterService.state(), ClusterState.EMPTY_STATE)); |
328 | | - awaitOrBust(fileChangeBarrier); |
329 | | - |
330 | | - // Now break the JSON |
331 | 315 | writeTestFile(fileSettingsService.watchedFile(), "test_invalid_JSON"); |
332 | 316 | awaitOrBust(fileChangeBarrier); |
333 | 317 |
|
334 | | - verify(fileSettingsService, times(1)).processFileOnServiceStart(); // The initial state |
335 | | - verify(fileSettingsService, times(1)).processFileChanges(); // The changed state |
336 | 318 | verify(fileSettingsService, times(1)).onProcessFileChangesException( |
337 | | - argThat(e -> e instanceof ExecutionException && e.getCause() instanceof XContentParseException) |
| 319 | + argThat(e -> unwrapException(e) instanceof XContentParseException) |
338 | 320 | ); |
339 | 321 |
|
340 | 322 | // Note: the name "processFileOnServiceStart" is a bit misleading because it is not |
341 | 323 | // referring to fileSettingsService.start(). Rather, it is referring to the initialization |
342 | 324 | // of the watcher thread itself, which occurs asynchronously when clusterChanged is first called. |
343 | 325 |
|
344 | | - verify(healthIndicatorService, times(2)).changeOccurred(); |
345 | | - verify(healthIndicatorService, times(1)).successOccurred(); |
346 | | - verify(healthIndicatorService, times(1)).failureOccurred(argThat(s -> s.startsWith(IllegalArgumentException.class.getName()))); |
347 | | - verifyNoMoreInteractions(healthIndicatorService); |
| 326 | + verify(healthIndicatorService).failureOccurred(contains(XContentParseException.class.getName())); |
| 327 | + } |
| 328 | + |
| 329 | + /** |
| 330 | + * Looks for the ultimate cause of {@code e} by stripping off layers of bookkeeping exception wrappers. |
| 331 | + */ |
| 332 | + private Throwable unwrapException(Throwable e) { |
| 333 | + while (e != null) { |
| 334 | + if (e instanceof ExecutionException || e instanceof IllegalStateException) { |
| 335 | + e = e.getCause(); |
| 336 | + } else { |
| 337 | + break; |
| 338 | + } |
| 339 | + } |
| 340 | + return e; |
348 | 341 | } |
349 | 342 |
|
350 | 343 | private static void awaitOrBust(CyclicBarrier barrier) { |
|
0 commit comments