/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zookeeper.test;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.AsyncCallback.MultiCallback;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Op;
import org.apache.zookeeper.OpResult;
import org.apache.zookeeper.Transaction;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.OpResult.CheckResult;
import org.apache.zookeeper.OpResult.CreateResult;
import org.apache.zookeeper.OpResult.DeleteResult;
import org.apache.zookeeper.OpResult.ErrorResult;
import org.apache.zookeeper.OpResult.SetDataResult;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.SyncRequestProcessor;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class MultiTransactionTest extends ClientBase {
private static final Logger LOG = Logger.getLogger(MultiTransactionTest.class);
private ZooKeeper zk;
private ZooKeeper zk_chroot;
private final boolean useAsync;
public MultiTransactionTest(boolean useAsync) {
this.useAsync = useAsync;
}
@Parameters
public static Collection<Object[]> configs() {
return Arrays.asList(new Object[][] {
{ false }, { true },
});
}
@Before
public void setUp() throws Exception {
SyncRequestProcessor.setSnapCount(150);
super.setUp();
zk = createClient();
}
static class MultiResult {
int rc;
List<OpResult> results;
boolean finished = false;
}
private List<OpResult> multi(ZooKeeper zk, Iterable<Op> ops)
throws KeeperException, InterruptedException {
if (useAsync) {
final MultiResult res = new MultiResult();
zk.multi(ops, new MultiCallback() {
@Override
public void processResult(int rc, String path, Object ctx,
List<OpResult> opResults) {
synchronized (res) {
res.rc = rc;
res.results = opResults;
res.finished = true;
res.notifyAll();
}
}
}, null);
synchronized (res) {
while (!res.finished) {
res.wait();
}
}
if (KeeperException.Code.OK.intValue() != res.rc) {
KeeperException ke = KeeperException.create(KeeperException.Code.get(res.rc));
throw ke;
}
return res.results;
} else {
return zk.multi(ops);
}
}
private void multiHavingErrors(ZooKeeper zk, Iterable<Op> ops,
List<Integer> expectedResultCodes, String expectedErr)
throws KeeperException, InterruptedException {
if (useAsync) {
final MultiResult res = new MultiResult();
zk.multi(ops, new MultiCallback() {
@Override
public void processResult(int rc, String path, Object ctx,
List<OpResult> opResults) {
synchronized (res) {
res.rc = rc;
res.results = opResults;
res.finished = true;
res.notifyAll();
}
}
}, null);
synchronized (res) {
while (!res.finished) {
res.wait();
}
}
for (int i = 0; i < res.results.size(); i++) {
OpResult opResult = res.results.get(i);
Assert.assertTrue("Did't recieve proper error response",
opResult instanceof ErrorResult);
ErrorResult errRes = (ErrorResult) opResult;
Assert.assertEquals("Did't recieve proper error code",
expectedResultCodes.get(i).intValue(), errRes.getErr());
}
} else {
try {
zk.multi(ops);
Assert.fail("Shouldn't have validated in ZooKeeper client!");
} catch (KeeperException e) {
Assert.assertEquals("Wrong exception", expectedErr, e.code()
.name());
} catch (IllegalArgumentException e) {
Assert.assertEquals("Wrong exception", expectedErr,
e.getMessage());
}
}
}
private List<OpResult> commit(Transaction txn)
throws KeeperException, InterruptedException {
if (useAsync) {
final MultiResult res = new MultiResult();
txn.commit(new MultiCallback() {
@Override
public void processResult(int rc, String path, Object ctx,
List<OpResult> opResults) {
synchronized (res) {
res.rc = rc;
res.results = opResults;
res.finished = true;
res.notifyAll();
}
}
}, null);
synchronized (res) {
while (!res.finished) {
res.wait();
}
}
if (KeeperException.Code.OK.intValue() != res.rc) {
KeeperException ke = KeeperException.create(KeeperException.Code.get(res.rc));
throw ke;
}
return res.results;
} else {
return txn.commit();
}
}
/**
* Test verifies the multi calls with invalid znode path
*/
@Test(timeout = 90000)
public void testInvalidPath() throws Exception {
List<Integer> expectedResultCodes = new ArrayList<Integer>();
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue());
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
// create with CreateMode
List<Op> opList = Arrays.asList(Op.create("/multi0", new byte[0],
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create(
"/multi1/", new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT), Op.create("/multi2", new byte[0],
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
String expectedErr = "Path must not end with / character";
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
// create with valid sequential flag
opList = Arrays.asList(Op.create("/multi0", new byte[0],
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create(
"multi1/", new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL.toFlag()), Op.create("/multi2",
new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
expectedErr = "Path must start with / character";
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
// check
opList = Arrays.asList(Op.check("/multi0", -1),
Op.check("/multi1/", 100), Op.check("/multi2", 5));
expectedErr = "Path must not end with / character";
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
// delete
opList = Arrays.asList(Op.delete("/multi0", -1),
Op.delete("/multi1/", 100), Op.delete("/multi2", 5));
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
// Multiple bad arguments
expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue());
// setdata
opList = Arrays.asList(Op.setData("/multi0", new byte[0], -1),
Op.setData("/multi1/", new byte[0], -1),
Op.setData("/multi2", new byte[0], -1),
Op.setData("multi3", new byte[0], -1));
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
}
/**
* Test verifies the multi calls with blank znode path
*/
@Test(timeout = 90000)
public void testBlankPath() throws Exception {
List<Integer> expectedResultCodes = new ArrayList<Integer>();
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue());
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue());
// delete
String expectedErr = "Path cannot be null";
List<Op> opList = Arrays.asList(Op.delete("/multi0", -1),
Op.delete(null, 100), Op.delete("/multi2", 5),
Op.delete("", -1));
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
}
/**
* Test verifies the multi.create with invalid createModeFlag
*/
@Test(timeout = 90000)
public void testInvalidCreateModeFlag() throws Exception {
List<Integer> expectedResultCodes = new ArrayList<Integer>();
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue());
expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY
.intValue());
int createModeFlag = 6789;
List<Op> opList = Arrays.asList(Op.create("/multi0", new byte[0],
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create(
"/multi1", new byte[0], Ids.OPEN_ACL_UNSAFE, createModeFlag),
Op.create("/multi2", new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT));
String expectedErr = KeeperException.Code.BADARGUMENTS.name();
multiHavingErrors(zk, opList, expectedResultCodes, expectedErr);
}
@Test
public void testChRootCreateDelete() throws Exception {
// creating the subtree for chRoot clients.
String chRoot = createNameSpace();
// Creating child using chRoot client.
zk_chroot = createClient(this.hostPort + chRoot);
Op createChild = Op.create("/myid", new byte[0],
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
multi(zk_chroot, Arrays.asList(createChild));
Assert.assertNotNull("zNode is not created under chroot:" + chRoot, zk
.exists(chRoot + "/myid", false));
Assert.assertNotNull("zNode is not created under chroot:" + chRoot,
zk_chroot.exists("/myid", false));
Assert.assertNull("zNode is created directly under '/', ignored configured chroot",
zk.exists("/myid", false));
// Deleting child using chRoot client.
Op deleteChild = Op.delete("/myid", 0);
multi(zk_chroot, Arrays.asList(deleteChild));
Assert.assertNull("zNode exists under chroot:" + chRoot, zk.exists(
chRoot + "/myid", false));
Assert.assertNull("zNode exists under chroot:" + chRoot, zk_chroot
.exists("/myid", false));
}
@Test
public void testChRootSetData() throws Exception {
// creating the subtree for chRoot clients.
String chRoot = createNameSpace();
// setData using chRoot client.
zk_chroot = createClient(this.hostPort + chRoot);
String[] names = {"/multi0", "/multi1", "/multi2"};
List<Op> ops = new ArrayList<Op>();
for (int i = 0; i < names.length; i++) {
ops.add(Op.create(names[i], new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT));
ops.add(Op.setData(names[i], names[i].getBytes(), 0));
}
multi(zk_chroot, ops) ;
for (int i = 0; i < names.length; i++) {
Assert.assertArrayEquals("zNode data not matching", names[i]
.getBytes(), zk_chroot.getData(names[i], false, null));
}
}
@Test
public void testChRootCheck() throws Exception {
// creating the subtree for chRoot clients.
String chRoot = createNameSpace();
// checking the child version using chRoot client.
zk_chroot = createClient(this.hostPort + chRoot);
String[] names = {"/multi0", "/multi1", "/multi2"};
List<Op> ops = new ArrayList<Op>();
for (int i = 0; i < names.length; i++) {
zk.create(chRoot + names[i], new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
for (int i = 0; i < names.length; i++) {
ops.add(Op.check(names[i], 0));
}
multi(zk_chroot, ops) ;
}
@Test
public void testChRootTransaction() throws Exception {
// creating the subtree for chRoot clients.
String chRoot = createNameSpace();
// checking the child version using chRoot client.
zk_chroot = createClient(this.hostPort + chRoot);
String childPath = "/myid";
Transaction transaction = zk_chroot.transaction();
transaction.create(childPath, new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
transaction.check(childPath, 0);
transaction.setData(childPath, childPath.getBytes(), 0);
commit(transaction);
Assert.assertNotNull("zNode is not created under chroot:" + chRoot, zk
.exists(chRoot + childPath, false));
Assert.assertNotNull("zNode is not created under chroot:" + chRoot,
zk_chroot.exists(childPath, false));
Assert.assertNull("zNode is created directly under '/', ignored configured chroot",
zk.exists(childPath, false));
Assert.assertArrayEquals("zNode data not matching", childPath
.getBytes(), zk_chroot.getData(childPath, false, null));
transaction = zk_chroot.transaction();
// Deleting child using chRoot client.
transaction.delete(childPath, 1);
commit(transaction);
Assert.assertNull("chroot:" + chRoot + " exists after delete", zk
.exists(chRoot + "/myid", false));
Assert.assertNull("chroot:" + chRoot + " exists after delete",
zk_chroot.exists("/myid", false));
}
private String createNameSpace() throws InterruptedException,
KeeperException {
// creating the subtree for chRoot clients.
String chRoot = "/appsX";
Op createChRoot = Op.create(chRoot, new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
multi(zk, Arrays.asList(createChRoot));
return chRoot;
}
@Test
public void testCreate() throws Exception {
multi(zk, Arrays.asList(
Op.create("/multi0", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.create("/multi1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.create("/multi2", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
));
zk.getData("/multi0", false, null);
zk.getData("/multi1", false, null);
zk.getData("/multi2", false, null);
}
@Test
public void testCreateDelete() throws Exception {
multi(zk, Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/multi", 0)
));
// '/multi' should have been deleted
Assert.assertNull(zk.exists("/multi", null));
}
@Test
public void testInvalidVersion() throws Exception {
try {
multi(zk, Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/multi", 1)
));
Assert.fail("delete /multi should have failed");
} catch (KeeperException e) {
/* PASS */
}
}
@Test
public void testNestedCreate() throws Exception {
multi(zk, Arrays.asList(
/* Create */
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.create("/multi/a", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.create("/multi/a/1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
/* Delete */
Op.delete("/multi/a/1", 0),
Op.delete("/multi/a", 0),
Op.delete("/multi", 0)
));
//Verify tree deleted
Assert.assertNull(zk.exists("/multi/a/1", null));
Assert.assertNull(zk.exists("/multi/a", null));
Assert.assertNull(zk.exists("/multi", null));
}
@Test
public void testSetData() throws Exception {
String[] names = {"/multi0", "/multi1", "/multi2"};
List<Op> ops = new ArrayList<Op>();
for (int i = 0; i < names.length; i++) {
ops.add(Op.create(names[i], new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
ops.add(Op.setData(names[i], names[i].getBytes(), 0));
}
multi(zk, ops) ;
for (int i = 0; i < names.length; i++) {
Assert.assertArrayEquals(names[i].getBytes(), zk.getData(names[i], false, null));
}
}
@Test
public void testUpdateConflict() throws Exception {
Assert.assertNull(zk.exists("/multi", null));
try {
multi(zk, Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.setData("/multi", "X".getBytes(), 0),
Op.setData("/multi", "Y".getBytes(), 0)
));
Assert.fail("Should have thrown a KeeperException for invalid version");
} catch (KeeperException e) {
//PASS
LOG.error("STACKTRACE: " + e);
}
Assert.assertNull(zk.exists("/multi", null));
//Updating version solves conflict -- order matters
multi(zk, Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.setData("/multi", "X".getBytes(), 0),
Op.setData("/multi", "Y".getBytes(), 1)
));
Assert.assertArrayEquals(zk.getData("/multi", false, null), "Y".getBytes());
}
@Test
public void testDeleteUpdateConflict() throws Exception {
/* Delete of a node folowed by an update of the (now) deleted node */
try {
multi(zk, Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/multi", 0),
Op.setData("/multi", "Y".getBytes(), 0)
));
Assert.fail("/multi should have been deleted so setData should have failed");
} catch (KeeperException e) {
/* PASS */
}
// '/multi' should never have been created as entire op should fail
Assert.assertNull(zk.exists("/multi", null)) ;
}
@Test
public void testGetResults() throws Exception {
/* Delete of a node folowed by an update of the (now) deleted node */
Iterable<Op> ops = Arrays.asList(
Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/multi", 0),
Op.setData("/multi", "Y".getBytes(), 0),
Op.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
);
List<OpResult> results = null;
if (useAsync) {
final MultiResult res = new MultiResult();
zk.multi(ops, new MultiCallback() {
@Override
public void processResult(int rc, String path, Object ctx,
List<OpResult> opResults) {
synchronized (res) {
res.rc = rc;
res.results = opResults;
res.finished = true;
res.notifyAll();
}
}
}, null);
synchronized (res) {
while (!res.finished) {
res.wait();
}
}
Assert.assertFalse("/multi should have been deleted so setData should have failed",
KeeperException.Code.OK.intValue() == res.rc);
Assert.assertNull(zk.exists("/multi", null));
results = res.results;
} else {
try {
zk.multi(ops);
Assert.fail("/multi should have been deleted so setData should have failed");
} catch (KeeperException e) {
// '/multi' should never have been created as entire op should fail
Assert.assertNull(zk.exists("/multi", null));
results = e.getResults();
}
}
Assert.assertNotNull(results);
for (OpResult r : results) {
LOG.info("RESULT==> " + r);
if (r instanceof ErrorResult) {
ErrorResult er = (ErrorResult) r;
LOG.info("ERROR RESULT: " + er + " ERR=>" + KeeperException.Code.get(er.getErr()));
}
}
}
/**
* Exercise the equals methods of OpResult classes.
*/
@Test
public void testOpResultEquals() {
opEquals(new CreateResult("/foo"),
new CreateResult("/foo"),
new CreateResult("nope"));
opEquals(new CreateResult("/foo"),
new CreateResult("/foo"),
new CreateResult("/foo", new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)));
opEquals(new CreateResult("/foo", new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new CreateResult("/foo", new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new CreateResult("nope", new Stat(11, 12, 13, 14, 15, 16, 17, 18, 19, 110, 111)));
opEquals(new CreateResult("/foo", new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new CreateResult("/foo", new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new CreateResult("/foo"));
opEquals(new CheckResult(),
new CheckResult(),
null);
opEquals(new SetDataResult(new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new SetDataResult(new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)),
new SetDataResult(new Stat(11, 12, 13, 14, 15, 16, 17, 18, 19, 110, 111)));
opEquals(new ErrorResult(1),
new ErrorResult(1),
new ErrorResult(2));
opEquals(new DeleteResult(),
new DeleteResult(),
null);
opEquals(new ErrorResult(1),
new ErrorResult(1),
new ErrorResult(2));
}
private void opEquals(OpResult expected, OpResult value, OpResult near) {
assertEquals(value, value);
assertFalse(value.equals(new Object()));
assertFalse(value.equals(near));
assertFalse(value.equals(value instanceof CreateResult ?
new ErrorResult(1) : new CreateResult("nope2")));
assertTrue(value.equals(expected));
}
@Test
public void testWatchesTriggered() throws KeeperException, InterruptedException {
HasTriggeredWatcher watcher = new HasTriggeredWatcher();
zk.getChildren("/", watcher);
multi(zk, Arrays.asList(
Op.create("/t", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/t", -1)
));
assertTrue(watcher.triggered.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS));
}
@Test
public void testNoWatchesTriggeredForFailedMultiRequest() throws InterruptedException, KeeperException {
HasTriggeredWatcher watcher = new HasTriggeredWatcher();
zk.getChildren("/", watcher);
try {
multi(zk, Arrays.asList(
Op.create("/t", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT),
Op.delete("/nonexisting", -1)
));
fail("expected previous multi op to fail!");
} catch (KeeperException.NoNodeException e) {
// expected
}
SyncCallback cb = new SyncCallback();
zk.sync("/", cb, null);
// by waiting for the callback we're assured that the event queue is flushed
cb.done.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
assertEquals(1, watcher.triggered.getCount());
}
@Test
public void testTransactionBuilder() throws Exception {
List<OpResult> results = commit(zk.transaction()
.create("/t1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
.create("/t1/child", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
.create("/t2", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL));
assertEquals(3, results.size());
for (OpResult r : results) {
CreateResult c = (CreateResult)r;
assertTrue(c.getPath().startsWith("/t"));
assertNotNull(c.toString());
}
assertNotNull(zk.exists("/t1", false));
assertNotNull(zk.exists("/t1/child", false));
assertNotNull(zk.exists("/t2", false));
results = commit(zk.transaction()
.check("/t1", 0)
.check("/t1/child", 0)
.check("/t2", 0));
assertEquals(3, results.size());
for (OpResult r : results) {
CheckResult c = (CheckResult)r;
assertNotNull(c.toString());
}
try {
results = commit(zk.transaction()
.check("/t1", 0)
.check("/t1/child", 0)
.check("/t2", 1));
fail();
} catch (KeeperException.BadVersionException e) {
// expected
}
results = commit(zk.transaction()
.check("/t1", 0)
.setData("/t1", new byte[0], 0));
assertEquals(2, results.size());
for (OpResult r : results) {
assertNotNull(r.toString());
}
try {
results = commit(zk.transaction()
.check("/t1", 1)
.setData("/t1", new byte[0], 2));
fail();
} catch (KeeperException.BadVersionException e) {
// expected
}
results = commit(zk.transaction()
.check("/t1", 1)
.check("/t1/child", 0)
.check("/t2", 0));
assertEquals(3, results.size());
results = commit(zk.transaction()
.delete("/t2", -1)
.delete("/t1/child", -1));
assertEquals(2, results.size());
for (OpResult r : results) {
DeleteResult d = (DeleteResult)r;
assertNotNull(d.toString());
}
assertNotNull(zk.exists("/t1", false));
assertNull(zk.exists("/t1/child", false));
assertNull(zk.exists("/t2", false));
}
private static class HasTriggeredWatcher implements Watcher {
private final CountDownLatch triggered = new CountDownLatch(1);
@Override
public void process(WatchedEvent event) {
triggered.countDown();
}
}
private static class SyncCallback implements AsyncCallback.VoidCallback {
private final CountDownLatch done = new CountDownLatch(1);
@Override
public void processResult(int rc, String path, Object ctx) {
done.countDown();
}
}
}