org.neo4j.consistency.checking.full.ExecutionOrderIntegrationTest.java Source code

Java tutorial

Introduction

Here is the source code for org.neo4j.consistency.checking.full.ExecutionOrderIntegrationTest.java

Source

/*
 * Copyright (c) 2002-2016 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.consistency.checking.full;

import org.apache.commons.lang3.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.neo4j.consistency.checking.CheckDecorator;
import org.neo4j.consistency.checking.CheckerEngine;
import org.neo4j.consistency.checking.ComparativeRecordChecker;
import org.neo4j.consistency.checking.GraphStoreFixture;
import org.neo4j.consistency.checking.OwningRecordCheck;
import org.neo4j.consistency.checking.RecordCheck;
import org.neo4j.consistency.checking.cache.CacheAccess;
import org.neo4j.consistency.checking.cache.DefaultCacheAccess;
import org.neo4j.consistency.report.ConsistencyReport;
import org.neo4j.consistency.report.ConsistencySummaryStatistics;
import org.neo4j.consistency.report.InconsistencyLogger;
import org.neo4j.consistency.report.InconsistencyReport;
import org.neo4j.consistency.report.PendingReferenceCheck;
import org.neo4j.consistency.statistics.DefaultCounts;
import org.neo4j.consistency.statistics.Statistics;
import org.neo4j.consistency.store.RecordAccess;
import org.neo4j.consistency.store.RecordReference;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.progress.ProgressMonitorFactory;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.factory.GraphDatabaseFacadeFactory;
import org.neo4j.kernel.impl.store.StoreAccess;
import org.neo4j.kernel.impl.store.record.AbstractBaseRecord;
import org.neo4j.kernel.impl.store.record.DynamicRecord;
import org.neo4j.kernel.impl.store.record.LabelTokenRecord;
import org.neo4j.kernel.impl.store.record.NeoStoreRecord;
import org.neo4j.kernel.impl.store.record.NodeRecord;
import org.neo4j.kernel.impl.store.record.PrimitiveRecord;
import org.neo4j.kernel.impl.store.record.PropertyKeyTokenRecord;
import org.neo4j.kernel.impl.store.record.PropertyRecord;
import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.kernel.impl.store.record.RelationshipTypeTokenRecord;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.withSettings;
import static org.neo4j.consistency.ConsistencyCheckService.defaultConsistencyCheckThreadsNumber;
import static org.neo4j.consistency.report.ConsistencyReporter.NO_MONITOR;
import static org.neo4j.graphdb.Label.label;
import static org.neo4j.helpers.collection.MapUtil.stringMap;
import static org.neo4j.test.Property.property;
import static org.neo4j.test.Property.set;

public class ExecutionOrderIntegrationTest {
    @Rule
    public final GraphStoreFixture fixture = new GraphStoreFixture(getRecordFormatName()) {
        @Override
        protected void generateInitialData(GraphDatabaseService graphDb) {
            // TODO: create bigger sample graph here
            try (org.neo4j.graphdb.Transaction tx = graphDb.beginTx()) {
                Node node1 = set(graphDb.createNode(label("Foo")));
                Node node2 = set(graphDb.createNode(label("Foo")), property("key", "value"));
                node1.createRelationshipTo(node2, RelationshipType.withName("C"));
                tx.success();
            }
        }
    };

    @Test
    public void shouldRunChecksInSingleThreadedPass() throws Exception {
        // given
        StoreAccess store = fixture.directStoreAccess().nativeStores();
        int threads = defaultConsistencyCheckThreadsNumber();
        CacheAccess cacheAccess = new DefaultCacheAccess(new DefaultCounts(threads), threads);
        RecordAccess access = FullCheck.recordAccess(store, cacheAccess);

        FullCheck singlePass = new FullCheck(getTuningConfiguration(), ProgressMonitorFactory.NONE, Statistics.NONE,
                threads);

        ConsistencySummaryStatistics singlePassSummary = new ConsistencySummaryStatistics();
        InconsistencyLogger logger = mock(InconsistencyLogger.class);
        InvocationLog singlePassChecks = new InvocationLog();

        // when
        singlePass.execute(fixture.directStoreAccess(), new LogDecorator(singlePassChecks), access,
                new InconsistencyReport(logger, singlePassSummary), cacheAccess, NO_MONITOR);

        // then
        verifyZeroInteractions(logger);
        assertEquals("Expected no inconsistencies in single pass.", 0,
                singlePassSummary.getTotalInconsistencyCount());
    }

    private Config getTuningConfiguration() {
        return new Config(stringMap(GraphDatabaseSettings.pagecache_memory.name(), "8m",
                GraphDatabaseFacadeFactory.Configuration.record_format.name(), getRecordFormatName()));
    }

    protected String getRecordFormatName() {
        return StringUtils.EMPTY;
    }

    private static class InvocationLog {
        private final Map<String, Throwable> data = new HashMap<>();
        private final Map<String, Integer> duplicates = new HashMap<>();

        @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
        void log(PendingReferenceCheck<?> check, InvocationOnMock invocation) {
            Method method = invocation.getMethod();
            if (Object.class == method.getDeclaringClass() && "finalize".equals(method.getName())) {
                /* skip invocations to finalize - they are not of interest to us,
                 * and GC is not predictable enough to reliably trace this. */
                return;
            }
            StringBuilder entry = new StringBuilder(method.getName()).append('(');
            entry.append(check);
            for (Object arg : invocation.getArguments()) {
                if (arg instanceof AbstractBaseRecord) {
                    AbstractBaseRecord record = (AbstractBaseRecord) arg;
                    entry.append(',').append(record.getClass().getSimpleName()).append('[').append(record.getId())
                            .append(']');
                }
            }
            String message = entry.append(')').toString();
            if (null != data.put(message, new Throwable(message))) {
                Integer cur = duplicates.get(message);
                if (cur == null) {
                    cur = 1;
                }
                duplicates.put(message, cur + 1);
            }
        }
    }

    private static void assertSameChecks(Map<String, Throwable> singlePassChecks,
            Map<String, Throwable> multiPassChecks) {
        if (!singlePassChecks.keySet().equals(multiPassChecks.keySet())) {
            Map<String, Throwable> missing = new HashMap<>(singlePassChecks);
            Map<String, Throwable> extras = new HashMap<>(multiPassChecks);
            missing.keySet().removeAll(multiPassChecks.keySet());
            extras.keySet().removeAll(singlePassChecks.keySet());

            StringBuilder headers = new StringBuilder("\n");
            StringWriter diff = new StringWriter();
            PrintWriter writer = new PrintWriter(diff);
            if (!missing.isEmpty()) {
                writer.append("These expected checks were missing:\n");
                for (Map.Entry<String, Throwable> check : missing.entrySet()) {
                    writer.append("  ");
                    headers.append("Missing: ").append(check.getKey()).append("\n");
                    check.getValue().printStackTrace(writer);
                }
            }
            if (!extras.isEmpty()) {
                writer.append("These extra checks were not expected:\n");
                for (Map.Entry<String, Throwable> check : extras.entrySet()) {
                    writer.append("  ");
                    headers.append("Unexpected: ").append(check.getKey()).append("\n");
                    check.getValue().printStackTrace(writer);
                }
            }
            fail(headers.toString() + diff.toString());
        }
    }

    private static class LogDecorator implements CheckDecorator {
        private final InvocationLog log;

        LogDecorator(InvocationLog log) {
            this.log = log;
        }

        @Override
        public void prepare() {
        }

        <REC extends AbstractBaseRecord, REP extends ConsistencyReport> OwningRecordCheck<REC, REP> logging(
                RecordCheck<REC, REP> checker) {
            return new LoggingChecker<>(checker, log);
        }

        @Override
        public OwningRecordCheck<NeoStoreRecord, ConsistencyReport.NeoStoreConsistencyReport> decorateNeoStoreChecker(
                OwningRecordCheck<NeoStoreRecord, ConsistencyReport.NeoStoreConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public OwningRecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> decorateNodeChecker(
                OwningRecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public OwningRecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> decorateRelationshipChecker(
                OwningRecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> decoratePropertyChecker(
                RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<PropertyKeyTokenRecord, ConsistencyReport.PropertyKeyTokenConsistencyReport> decoratePropertyKeyTokenChecker(
                RecordCheck<PropertyKeyTokenRecord, ConsistencyReport.PropertyKeyTokenConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<RelationshipTypeTokenRecord, ConsistencyReport.RelationshipTypeConsistencyReport> decorateRelationshipTypeTokenChecker(
                RecordCheck<RelationshipTypeTokenRecord, ConsistencyReport.RelationshipTypeConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<LabelTokenRecord, ConsistencyReport.LabelTokenConsistencyReport> decorateLabelTokenChecker(
                RecordCheck<LabelTokenRecord, ConsistencyReport.LabelTokenConsistencyReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<NodeRecord, ConsistencyReport.LabelsMatchReport> decorateLabelMatchChecker(
                RecordCheck<NodeRecord, ConsistencyReport.LabelsMatchReport> checker) {
            return logging(checker);
        }

        @Override
        public RecordCheck<RelationshipGroupRecord, ConsistencyReport.RelationshipGroupConsistencyReport> decorateRelationshipGroupChecker(
                RecordCheck<RelationshipGroupRecord, ConsistencyReport.RelationshipGroupConsistencyReport> checker) {
            return logging(checker);
        }
    }

    private static class LoggingChecker<REC extends AbstractBaseRecord, REP extends ConsistencyReport>
            implements OwningRecordCheck<REC, REP> {
        private final RecordCheck<REC, REP> checker;
        private final InvocationLog log;

        LoggingChecker(RecordCheck<REC, REP> checker, InvocationLog log) {
            this.checker = checker;
            this.log = log;
        }

        @Override
        public void check(REC record, CheckerEngine<REC, REP> engine, RecordAccess records) {
            checker.check(record, engine, new ComparativeLogging(records, log));
        }

        @SuppressWarnings("unchecked")
        @Override
        public ComparativeRecordChecker<REC, PrimitiveRecord, REP> ownerCheck() {
            if (checker instanceof OwningRecordCheck) {
                return ((OwningRecordCheck) checker).ownerCheck();
            }

            throw new UnsupportedOperationException();
        }
    }

    private static class LoggingReference<T extends AbstractBaseRecord> implements RecordReference<T> {
        private final RecordReference<T> reference;
        private final InvocationLog log;

        LoggingReference(RecordReference<T> reference, InvocationLog log) {
            this.reference = reference;
            this.log = log;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void dispatch(PendingReferenceCheck<T> reporter) {
            reference.dispatch(mock((Class<PendingReferenceCheck<T>>) reporter.getClass(), withSettings()
                    .spiedInstance(reporter).defaultAnswer(new ReporterSpy<>(reference, reporter, log))));
        }
    }

    private static class ReporterSpy<T extends AbstractBaseRecord> implements Answer<Object> {
        private final RecordReference<T> reference;
        private final PendingReferenceCheck<T> reporter;
        private final InvocationLog log;

        public ReporterSpy(RecordReference<T> reference, PendingReferenceCheck<T> reporter, InvocationLog log) {
            this.reference = reference;
            this.reporter = reporter;
            this.log = log;
        }

        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            if (!(reference instanceof RecordReference.SkippingReference<?>)) {
                log.log(reporter, invocation);
            }
            return invocation.callRealMethod();
        }
    }

    private static class ComparativeLogging implements RecordAccess {
        private final RecordAccess access;
        private final InvocationLog log;

        ComparativeLogging(RecordAccess access, InvocationLog log) {
            this.access = access;
            this.log = log;
        }

        private <T extends AbstractBaseRecord> LoggingReference<T> logging(RecordReference<T> actual) {
            return new LoggingReference<>(actual, log);
        }

        @Override
        public RecordReference<DynamicRecord> schema(long id) {
            return logging(access.schema(id));
        }

        @Override
        public RecordReference<NodeRecord> node(long id) {
            return logging(access.node(id));
        }

        @Override
        public RecordReference<RelationshipRecord> relationship(long id) {
            return logging(access.relationship(id));
        }

        @Override
        public RecordReference<RelationshipGroupRecord> relationshipGroup(long id) {
            return logging(access.relationshipGroup(id));
        }

        @Override
        public RecordReference<PropertyRecord> property(long id) {
            return logging(access.property(id));
        }

        @Override
        public RecordReference<RelationshipTypeTokenRecord> relationshipType(int id) {
            return logging(access.relationshipType(id));
        }

        @Override
        public RecordReference<PropertyKeyTokenRecord> propertyKey(int id) {
            return logging(access.propertyKey(id));
        }

        @Override
        public RecordReference<DynamicRecord> string(long id) {
            return logging(access.string(id));
        }

        @Override
        public RecordReference<DynamicRecord> array(long id) {
            return logging(access.array(id));
        }

        @Override
        public RecordReference<DynamicRecord> relationshipTypeName(int id) {
            return logging(access.relationshipTypeName(id));
        }

        @Override
        public RecordReference<DynamicRecord> nodeLabels(long id) {
            return logging(access.nodeLabels(id));
        }

        @Override
        public RecordReference<LabelTokenRecord> label(int id) {
            return logging(access.label(id));
        }

        @Override
        public RecordReference<DynamicRecord> labelName(int id) {
            return logging(access.labelName(id));
        }

        @Override
        public RecordReference<DynamicRecord> propertyKeyName(int id) {
            return logging(access.propertyKeyName(id));
        }

        @Override
        public RecordReference<NeoStoreRecord> graph() {
            return logging(access.graph());
        }

        @Override
        public boolean shouldCheck(long id, MultiPassStore store) {
            return access.shouldCheck(id, store);
        }

        @Override
        public CacheAccess cacheAccess() {
            return access.cacheAccess();
        }

        @Override
        public Iterator<PropertyRecord> rawPropertyChain(long firstId) {
            return access.rawPropertyChain(firstId);
        }
    }
}