public CompletionStage<Void> migrate(MigrationSet set) {
        InterProcessLock lock = new InterProcessSemaphoreMutex(client.unwrap(), ZKPaths.makePath(lockPath, set.id()));
        CompletionStage<Void> lockStage = lockAsync(lock, lockMax.toMillis(), TimeUnit.MILLISECONDS, executor);
        return lockStage.thenCompose(__ -> runMigrationInLock(lock, set));
    }
@Test
    public void testBasic() {
        Migration m1 = () -> Arrays.asList(v1opA, v1opB);
        Migration m2 = () -> Collections.singletonList(v2op);
        Migration m3 = () -> Collections.singletonList(v3op);
        MigrationSet migrationSet = MigrationSet.build("1", Arrays.asList(m1, m2, m3));

        complete(manager.migrate(migrationSet));

        ModeledFramework<ModelV3> v3Client = ModeledFramework.wrap(client, v3Spec);
        complete(v3Client.read(), (m, e) -> {
            assertEquals(m.getAge(), 30);
            assertEquals(m.getFirstName(), "One");
            assertEquals(m.getLastName(), "Two");
        });

        int count = manager.debugCount.get();
        complete(manager.migrate(migrationSet));
        assertEquals(manager.debugCount.get(), count); // second call should do nothing
    }