static PendingRestarts triggerPendingRestarts(Function<Set<String>, ServiceListResponse> convergenceChecker,
                                                  BiConsumer<ApplicationId, Set<String>> restarter,
                                                  ApplicationId id,
                                                  PendingRestarts restarts,
                                                  Logger log) {
        Set<String> restartingHosts = restarts.hostnames();
        if (restartingHosts.isEmpty()) return restarts;

        ServiceListResponse convergence = convergenceChecker.apply(restartingHosts);
        long lowestGeneration = convergence.currentGeneration;
        Set<String> nodesToRestart = restarts.restartsReadyAt(lowestGeneration);
        if (nodesToRestart.isEmpty()) {
            log.info(String.format("Cannot yet restart nodes of %s, as some services are still on generation %d:\n\t%s",
                                   id.toFullString(),
                                   lowestGeneration,
                                   convergence.services().stream()
                                              .filter(service -> service.currentGeneration == lowestGeneration)
                                              .map(service -> service.serviceInfo.getHostName() + ":" + service.serviceInfo.getServiceName())
                                              .collect(Collectors.joining("\n\t"))));
            return restarts;
        }

        restarter.accept(id, nodesToRestart);
        log.info(String.format("Scheduled restart of %d nodes after observing generation %d: %s",
                               nodesToRestart.size(), lowestGeneration, nodesToRestart.stream().sorted().collect(Collectors.joining(", "))));

        return restarts.withoutPreviousGenerations(lowestGeneration);
    }
@Test
    void testMaintenance() {
        // Nothing happens with no pending restarts.
        assertSame(PendingRestarts.empty(),
                   triggerPendingRestarts(hosts -> { fail("Should not be called"); return null; },
                                          (id, hosts) -> { fail("Should not be called"); },
                                          ApplicationId.defaultId(),
                                          PendingRestarts.empty(),
                                          log));

        // Nothing happens when services are on a too low generation.
        assertEquals(Map.of(1L, Set.of("a", "b"), 2L, Set.of("c")),
                     triggerPendingRestarts(hosts -> new ServiceListResponse(Map.of(), 3, 0),
                                            (id, hosts) -> { fail("Should not be called"); },
                                            ApplicationId.defaultId(),
                                            PendingRestarts.empty()
                                                           .withRestarts(1, List.of("a", "b"))
                                                           .withRestarts(2, List.of("c")),
                                            log)
                             .generationsForRestarts());

        // Only the first hosts are restarted before the second generation is reached.
        assertEquals(Map.of(2L, Set.of("c")),
                     triggerPendingRestarts(hosts -> new ServiceListResponse(Map.of(), 3, 1),
                                            (id, hosts) -> {
                                                assertEquals(ApplicationId.defaultId(), id);
                                                assertEquals(Set.of("a", "b"), hosts);
                                            },
                                            ApplicationId.defaultId(),
                                            PendingRestarts.empty()
                                                           .withRestarts(1, List.of("a", "b"))
                                                           .withRestarts(2, List.of("c")),
                                            log)
                             .generationsForRestarts());

        // All hosts are restarted when the second generation is reached.
        assertEquals(Map.of(),
                     triggerPendingRestarts(hosts -> new ServiceListResponse(Map.of(), 3, 2),
                                            (id, hosts) -> {
                                                assertEquals(ApplicationId.defaultId(), id);
                                                assertEquals(Set.of("a", "b", "c"), hosts);
                                            },
                                            ApplicationId.defaultId(),
                                            PendingRestarts.empty()
                                                           .withRestarts(1, List.of("a", "b"))
                                                           .withRestarts(2, List.of("c")),
                                            log)
                             .generationsForRestarts());

    }