blob: 4a7a572d73b29e8c5a9766897776516167ef5221 [file] [log] [blame]
/*
* 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.jackrabbit.oak.plugins.document;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Test;
/**
* Tests the revision class
*/
public class RevisionTest {
@Test
public void invalid() {
// revisions need to start with "br" or "r"
for(String s : "1234,b,bb,".split(",")) {
try {
Revision.fromString(s);
fail("Expected: Invalid revision id exception for " + s);
} catch (Exception expected) {
// expected
}
}
}
@Test
public void edgeCases() {
assertEquals("br0-0-0", new Revision(0, 0, 0, true).toString());
Random rand = new Random(0);
for (int i = 0; i < 1000; i++) {
Revision r = new Revision(rand.nextLong(), rand.nextInt(),
rand.nextInt(), rand.nextBoolean());
assertEquals(r.toString(), Revision.fromString(r.toString()).toString());
}
for (int i = 0; i < 1000; i++) {
Revision r = new Revision(rand.nextInt(10), rand.nextInt(10),
rand.nextInt(10), rand.nextBoolean());
assertEquals(r.toString(), Revision.fromString(r.toString()).toString());
}
}
@Test
public void fromStringToString() {
for (int i = 0; i < 10000; i++) {
Revision r = Revision.newRevision(i);
// System.out.println(r);
String rs = r.toString();
Revision r2 = Revision.fromString(rs);
if(!rs.equals(r2.toString())) {
r2 = Revision.fromString(rs);
assertEquals(rs, r2.toString());
}
assertEquals(rs, r2.toString());
assertEquals(r.hashCode(), r2.hashCode());
assertTrue(r.equals(r2));
}
}
@Test
public void difference() throws InterruptedException {
long t0 = Revision.getCurrentTimestamp();
Revision r0 = Revision.newRevision(0);
Revision r1 = Revision.newRevision(0);
long t1 = Revision.getCurrentTimestamp();
// the difference must not be more than t1 - t0
assertTrue(Revision.getTimestampDifference(r1, r0) <= (t1 - t0));
// busy wait until we have a timestamp different from t1
long t2;
do {
t2 = Revision.getCurrentTimestamp();
} while (t1 == t2);
Revision r2 = Revision.newRevision(0);
assertTrue(Revision.getTimestampDifference(r2, r1) > 0);
}
@Test
public void equalsHashCode() {
Revision a = Revision.newRevision(0);
Revision b = Revision.newRevision(0);
assertTrue(a.equals(a));
assertFalse(a.equals(b));
assertFalse(b.equals(a));
assertFalse(a.hashCode() == b.hashCode());
Revision a1 = Revision.fromString(a.toString());
assertTrue(a.equals(a1));
assertTrue(a1.equals(a));
Revision a2 = new Revision(a.getTimestamp(), a.getCounter(), a.getClusterId());
assertTrue(a.equals(a2));
assertTrue(a2.equals(a));
assertEquals(a.hashCode(), a1.hashCode());
assertEquals(a.hashCode(), a2.hashCode());
Revision x1 = new Revision(a.getTimestamp() + 1, a.getCounter(), a.getClusterId());
assertFalse(a.equals(x1));
assertFalse(x1.equals(a));
assertFalse(a.hashCode() == x1.hashCode());
Revision x2 = new Revision(a.getTimestamp(), a.getCounter() + 1, a.getClusterId());
assertFalse(a.equals(x2));
assertFalse(x2.equals(a));
assertFalse(a.hashCode() == x2.hashCode());
Revision x3 = new Revision(a.getTimestamp(), a.getCounter(), a.getClusterId() + 1);
assertFalse(a.equals(x3));
assertFalse(x3.equals(a));
assertFalse(a.hashCode() == x3.hashCode());
}
@Test
public void compare() throws InterruptedException {
Revision last = Revision.newRevision(0);
try {
last.compareRevisionTime(null);
fail();
} catch (NullPointerException e) {
// expected
}
for (int i = 0; i < 1000; i++) {
Revision r = Revision.newRevision(0);
assertTrue(r.compareRevisionTime(r) == 0);
assertTrue(r.compareRevisionTime(last) > 0);
assertTrue(last.compareRevisionTime(r) < 0);
last = r;
if (i % 100 == 0) {
// ensure the timestamp part changes as well
Thread.sleep(1);
}
}
}
@Test
public void uniqueRevision2() throws Exception {
List<Thread> threads = new ArrayList<Thread>();
final AtomicBoolean stop = new AtomicBoolean();
final Set<Revision> set = Collections
.synchronizedSet(new HashSet<Revision>());
final Revision[] duplicate = new Revision[1];
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Revision[] last = new Revision[1024];
while (!stop.get()) {
for (Revision r : last) {
set.remove(r);
}
for (int i = 0; i < last.length; i++) {
last[i] = Revision.newRevision(1);
}
for (Revision r : last) {
if (!set.add(r)) {
duplicate[0] = r;
}
}
}
}
});
thread.start();
threads.add(thread);
}
Thread.sleep(200);
stop.set(true);
for (Thread t : threads) {
t.join();
}
assertNull("Duplicate revision", duplicate[0]);
}
@Test
public void uniqueRevision() throws Exception {
//Revision.setClock(new Clock.Virtual());
final BlockingQueue<Revision> revisionQueue = Queues.newLinkedBlockingQueue();
int noOfThreads = 60;
final int noOfLoops = 1000;
List<Thread> workers = new ArrayList<Thread>();
final AtomicBoolean stop = new AtomicBoolean();
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch stopLatch = new CountDownLatch(noOfThreads);
for (int i = 0; i < noOfThreads; i++) {
workers.add(new Thread(new Runnable() {
@Override
public void run() {
Uninterruptibles.awaitUninterruptibly(startLatch);
for (int j = 0; j < noOfLoops && !stop.get(); j++) {
revisionQueue.add(Revision.newRevision(1));
}
stopLatch.countDown();
}
}));
}
final List<Revision> duplicates = Lists.newArrayList();
final Set<Revision> seenRevs = Sets.newHashSet();
workers.add(new Thread(new Runnable() {
@Override
public void run() {
startLatch.countDown();
while (!stop.get()) {
List<Revision> revs = Lists.newArrayList();
Queues.drainUninterruptibly(revisionQueue, revs, 5, 100, TimeUnit.MILLISECONDS);
record(revs);
}
List<Revision> revs = Lists.newArrayList();
revisionQueue.drainTo(revs);
record(revs);
}
private void record(List<Revision> revs) {
for (Revision rev : revs) {
if (!seenRevs.add(rev)) {
duplicates.add(rev);
}
}
if (!duplicates.isEmpty()) {
stop.set(true);
}
}
}));
for (Thread t : workers) {
t.start();
}
stopLatch.await();
stop.set(true);
for (Thread t : workers) {
t.join();
}
assertTrue(String.format("Duplicate rev seen %s %n Seen %s", duplicates, seenRevs), duplicates.isEmpty());
}
}