### Question:
IoRetry { public static void tryTimes(int times, IOAction task, Consumer<IOException> errorHandler) throws UncheckedIOException, IllegalArgumentException { if (times < 1) { throw new IllegalArgumentException("The number of retries should be a positive integer. It was " + times); } int retries = 0; while (true) { try { task.run(); return; } catch (IOException e) { onError(errorHandler, e); if (++retries == times) { throw new UncheckedIOException( format("Operation failed after %d retries: %s", times, e.getMessage()), e); } } } } private  IoRetry(); static void tryTimes(int times, IOAction task, Consumer<IOException> errorHandler);  }### Answer:
@Test public void runOnceIfNoErrors() throws IOException { tryTimes(2, task, null); verify(task).run(); }
@Test public void retryUpToMaxTimesForIoException() throws IOException { doThrow(new IOException("Could not connect")).when(task).run(); try { tryTimes(3, task, (e) -> assertThat(e, is(instanceOf(IOException.class)))); fail("An exception should have been thrown"); } catch (UncheckedIOException e) { assertThat(e.getMessage(), is("Operation failed after 3 retries: Could not connect")); } verify(task, times(3)).run(); }
@Test public void retryOnlyOnceIfSingleError() throws IOException { doThrow(new IOException()).doNothing().when(task).run(); tryTimes(3, task, (e) -> assertThat(e, is(instanceOf(IOException.class)))); verify(task, times(2)).run(); }
@Test public void noRetriesForOtherExceptions() throws IOException { doThrow(new NullPointerException()).doNothing().when(task).run(); try { tryTimes(3, task, null); fail("An exception should have been thrown"); } catch (NullPointerException e) { verify(task, times(1)).run(); } }
@Test public void illegalArgumentExceptionAndNoAttemptsForInvalidArgument() throws IOException { doNothing().when(task).run(); try { tryTimes(0, task, null); fail("An exception should have been thrown"); } catch (IllegalArgumentException e) { verify(task, times(0)).run(); } }