blob: aff8186fb3b3c988cb2ad2549d389261fe28c4ca [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.lang.reflect.Field;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.stats.Clock;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import ch.qos.logback.classic.Level;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
public class VersionGarbageCollectorLogTest {
private static final int BATCH_SIZE;
static {
try {
Field f = VersionGarbageCollector.class.getDeclaredField("DELETE_BATCH_SIZE");
f.setAccessible(true);
BATCH_SIZE = (int) f.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
@Rule
public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
private LogCustomizer logCustomizer = LogCustomizer.forLogger(
VersionGarbageCollector.class.getName()).enable(Level.INFO).create();
private Clock clock;
private DocumentNodeStore ns;
@Before
public void before() throws Exception {
clock = new Clock.Virtual();
clock.waitUntil(System.currentTimeMillis());
Revision.setClock(clock);
ns = new DocumentMK.Builder().setAsyncDelay(0).clock(clock).getNodeStore();
logCustomizer.starting();
}
@After
public void after() {
logCustomizer.finished();
}
@AfterClass
public static void resetClock() {
Revision.resetClockToDefault();
}
@Test
public void gc() throws Exception {
createGarbage();
clock.waitUntil(clock.getTime() + TimeUnit.HOURS.toMillis(1));
VersionGarbageCollector gc = ns.getVersionGarbageCollector();
gc.gc(30, TimeUnit.MINUTES);
List<String> messages = getDeleteMessages();
assertThat(messages.size(), greaterThan(0));
for (String msg : messages) {
assertThat(getNumDeleted(msg), lessThan(BATCH_SIZE + 1));
}
}
private int getNumDeleted(String msg) {
int idx = msg.indexOf('[');
return Integer.parseInt(msg.substring(idx + 1, msg.indexOf(']')));
}
private void createGarbage() throws Exception {
Random r = new Random(42);
String path = "/";
for (int i = 0; i < 1000; i++) {
int v = r.nextInt(10);
if (v == 0 || path.equals("/")) {
// create new top level node
path = "/node-" + i;
addNode(path);
} else {
addNode(path + "/node-" + i);
}
}
for (String name : ns.getRoot().getChildNodeNames()) {
if (name.startsWith("node-")) {
if (r.nextBoolean()) {
recreate(name);
} else {
remove(name);
}
}
}
}
private List<String> getDeleteMessages() {
List<String> messages = Lists.newArrayList();
for (String msg : logCustomizer.getLogs()) {
if (msg.startsWith("Proceeding to delete [")) {
messages.add(msg);
}
}
return messages;
}
private void remove(String name) throws Exception {
NodeBuilder builder = ns.getRoot().builder();
builder.child(name).remove();
TestUtils.merge(ns, builder);
}
private void recreate(String name) throws Exception {
NodeBuilder builder = ns.getRoot().builder();
builder.child(name).remove();
builder.child(name);
TestUtils.merge(ns, builder);
}
private void addNode(String path) throws Exception {
NodeBuilder builder = ns.getRoot().builder();
NodeBuilder b = builder;
for (String name : PathUtils.elements(path)) {
b = b.child(name);
}
TestUtils.merge(ns, builder);
}
}