public void waitUntilRegionIsReadyOnExactlyThisManyServers(String regionName,
      int exactServerCount) throws Exception {
    if (exactServerCount == 0) {
      waitUntilEqual(
          () -> getRegionMBean(regionName),
          Objects::isNull,
          true,
          String.format("Expecting to not find an mbean for region '%s'", regionName),
          WAIT_UNTIL_TIMEOUT, TimeUnit.SECONDS);
      return;
    }
    // First wait until the region mbean is not null...
    waitUntilEqual(
        () -> getRegionMBean(regionName),
        Objects::nonNull,
        true,
        String.format("Expecting to find an mbean for region '%s'", regionName),
        WAIT_UNTIL_TIMEOUT, TimeUnit.SECONDS);

    // Now actually wait for the members to receive the region
    String assertionConditionDescription = String.format(
        "Expecting region '%s' to be found on exactly %d servers", regionName, exactServerCount);
    waitUntilSatisfied(
        () -> Arrays.asList(getRegionMBean(regionName).getMembers()),
        Function.identity(),
        members -> Assertions.assertThat(members).isNotNull().hasSize(exactServerCount),
        assertionConditionDescription,
        WAIT_UNTIL_TIMEOUT, TimeUnit.SECONDS);
  }
the abstract class that's used by LocatorStarterRule and ServerStarterRule to avoid code
duplication.

The rule will try to clean up the working dir as best as it can. Any first level children
created in the test will be cleaned up after the test.
geode-dunit/src/main/java/org/apache/geode/test/junit/rules/MemberStarterRule.java