diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/FormatCompatibilityTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/FormatCompatibilityTest.java
deleted file mode 100644
index d06edef0de370..0000000000000
--- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/FormatCompatibilityTest.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (c) 2002-2018 "Neo4j,"
- * Neo4j Sweden AB [http://neo4j.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 .
- */
-package org.neo4j.index.internal.gbptree;
-
-import org.apache.commons.lang3.mutable.MutableLong;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import org.neo4j.cursor.RawCursor;
-import org.neo4j.io.compress.ZipUtils;
-import org.neo4j.io.pagecache.IOLimiter;
-import org.neo4j.io.pagecache.PageCache;
-import org.neo4j.test.rule.PageCacheRule;
-import org.neo4j.test.rule.RandomRule;
-import org.neo4j.test.rule.TestDirectory;
-import org.neo4j.test.rule.fs.DefaultFileSystemRule;
-
-import static java.lang.String.format;
-import static java.util.Arrays.asList;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.neo4j.index.internal.gbptree.SimpleLongLayout.longLayout;
-
-/**
- * A little trick to automatically tell whether or not index format was changed without
- * incrementing the format version. This is done by keeping a zipped tree which is opened and tested on.
- * On failure this test will fail saying that the format version needs update and also update the zipped
- * store with the new version.
- */
-@RunWith( Parameterized.class )
-public class FormatCompatibilityTest
-{
- private static final String STORE = "store";
- private static final int INITIAL_KEY_COUNT = 10_000;
- private static final String CURRENT_FIXED_SIZE_FORMAT_ZIP = "current-format.zip";
- private static final String CURRENT_DYNAMIC_SIZE_FORMAT_ZIP = "current-dynamic-format.zip";
-
- @Parameters
- public static List data()
- {
- return asList(
- new Object[] {longLayout().withFixedSize( true ).build(), CURRENT_FIXED_SIZE_FORMAT_ZIP},
- new Object[] {longLayout().withFixedSize( false ).build(), CURRENT_DYNAMIC_SIZE_FORMAT_ZIP} );
- }
-
- @Parameter
- public SimpleLongLayout layout;
- @Parameter( 1 )
- public String zipName;
-
- private final TestDirectory directory = TestDirectory.testDirectory();
- private final PageCacheRule pageCacheRule = new PageCacheRule();
- private final DefaultFileSystemRule fsRule = new DefaultFileSystemRule();
- private final RandomRule random = new RandomRule();
-
- @Rule
- public final RuleChain chain = RuleChain.outerRule( random ).around( fsRule ).around( directory ).around( pageCacheRule );
-
- @Test
- public void shouldDetectFormatChange() throws Throwable
- {
- List initialKeys = initialKeys();
- List keysToAdd = keysToAdd();
- List allKeys = new ArrayList<>();
- allKeys.addAll( initialKeys );
- allKeys.addAll( keysToAdd );
- allKeys.sort( Long::compare );
-
- // GIVEN stored tree
- File storeFile = directory.file( STORE );
- try
- {
- unzipTo( storeFile );
- }
- catch ( FileNotFoundException e )
- {
- // First time this test is run, eh?
- createAndZipTree( storeFile );
- tellDeveloperToCommitThisFormatVersion();
- }
- assertTrue( zipName + " seems to be missing from resources directory", fsRule.get().fileExists( storeFile ) );
-
- PageCache pageCache = pageCacheRule.getPageCache( fsRule.get() );
- try ( GBPTree tree =
- new GBPTreeBuilder<>( pageCache, storeFile, layout ).build() )
- {
- try
- {
- {
- // WHEN reading from the tree
- // THEN initial keys should be there
- tree.consistencyCheck();
- try ( RawCursor,IOException> cursor =
- tree.seek( layout.key( 0 ), layout.key( Long.MAX_VALUE ) ) )
- {
- for ( Long expectedKey : initialKeys )
- {
- assertHit( cursor, expectedKey );
- }
- assertFalse( cursor.next() );
- }
- }
-
- {
- // WHEN writing more to the tree
- // THEN we should not see any format conflicts
- try ( Writer writer = tree.writer() )
- {
- while ( keysToAdd.size() > 0 )
- {
- int next = random.nextInt( keysToAdd.size() );
- put( writer, keysToAdd.get( next ) );
- keysToAdd.remove( next );
- }
- }
- }
-
- {
- // WHEN reading from the tree again
- // THEN all keys including newly added should be there
- tree.consistencyCheck();
- try ( RawCursor,IOException> cursor =
- tree.seek( layout.key( 0 ), layout.key( 2 * INITIAL_KEY_COUNT ) ) )
- {
- for ( Long expectedKey : allKeys )
- {
- assertHit( cursor, expectedKey );
- }
- assertFalse( cursor.next() );
- }
- }
-
- {
- // WHEN randomly removing half of tree content
- // THEN we should not see any format conflicts
- try ( Writer writer = tree.writer() )
- {
- int size = allKeys.size();
- while ( allKeys.size() > size / 2 )
- {
- int next = random.nextInt( allKeys.size() );
- MutableLong key = layout.key( allKeys.get( next ) );
- writer.remove( key );
- allKeys.remove( next );
- }
- }
- }
-
- {
- // WHEN reading from the tree after remove
- // THEN we should see everything that is left in the tree
- tree.consistencyCheck();
- try ( RawCursor,IOException> cursor =
- tree.seek( layout.key( 0 ), layout.key( 2 * INITIAL_KEY_COUNT ) ) )
- {
- for ( Long expectedKey : allKeys )
- {
- assertHit( cursor, expectedKey );
- }
- assertFalse( cursor.next() );
- }
- }
- }
- catch ( Throwable t )
- {
- throw new AssertionError( "If this is the single failing test in this component then this failure is a strong indication that format " +
- "has changed without also incrementing TreeNode version(s). Please make necessary format version changes.", t );
- }
- }
- catch ( MetadataMismatchException e )
- {
- // Good actually, or?
- assertThat( e.getMessage(), containsString( "format version" ) );
-
- fsRule.get().deleteFile( storeFile );
- createAndZipTree( storeFile );
-
- tellDeveloperToCommitThisFormatVersion();
- }
- }
-
- private void tellDeveloperToCommitThisFormatVersion()
- {
- fail( format( "This is merely a notification to developer. Format has changed and its version has also " +
- "been properly incremented. A tree with this new format has been generated and should be committed. " +
- "Please move:%n %s%ninto %n %s, %nreplacing the existing file there",
- directory.file( zipName ),
- "" + pathify( ".src.test.resources." ) +
- pathify( getClass().getPackage().getName() + "." ) + zipName ) );
- }
-
- private static String pathify( String name )
- {
- return name.replace( '.', File.separatorChar );
- }
-
- private void unzipTo( File storeFile ) throws IOException
- {
- URL resource = getClass().getResource( zipName );
- if ( resource == null )
- {
- throw new FileNotFoundException();
- }
-
- try ( ZipFile zipFile = new ZipFile( resource.getFile() ) )
- {
- Enumeration extends ZipEntry> entries = zipFile.entries();
- assertTrue( entries.hasMoreElements() );
- ZipEntry entry = entries.nextElement();
- assertEquals( STORE, entry.getName() );
- Files.copy( zipFile.getInputStream( entry ), storeFile.toPath() );
- }
- }
-
- private void createAndZipTree( File storeFile ) throws IOException
- {
- List initialKeys = initialKeys();
- PageCache pageCache = pageCacheRule.getPageCache( fsRule.get() );
- try ( GBPTree tree =
- new GBPTreeBuilder<>( pageCache, storeFile, layout ).build() )
- {
- try ( Writer writer = tree.writer() )
- {
- for ( Long key : initialKeys )
- {
- put( writer, key );
- }
- }
- tree.checkpoint( IOLimiter.UNLIMITED );
- }
- ZipUtils.zip( fsRule.get(), storeFile, directory.file( zipName ) );
- }
-
- private static long value( long key )
- {
- return key * 2;
- }
-
- private List initialKeys()
- {
- List initialKeys = new ArrayList<>();
- for ( long i = 0, key = 0; i < INITIAL_KEY_COUNT; i++, key += 2 )
- {
- initialKeys.add( key );
- }
- return initialKeys;
- }
-
- private List keysToAdd()
- {
- List keysToAdd = new ArrayList<>();
- for ( long i = 0, key = 1; i < INITIAL_KEY_COUNT; i++, key += 2 )
- {
- keysToAdd.add( key );
- }
- return keysToAdd;
- }
-
- private void assertHit( RawCursor,IOException> cursor, Long expectedKey ) throws IOException
- {
- assert cursor.next() : "Had no next when expecting key " + expectedKey;
- Hit hit = cursor.get();
- assertEquals( expectedKey.longValue(), hit.key().longValue() );
- assertEquals( value( expectedKey ), hit.value().longValue() );
- }
-
- private void put( Writer writer, long key ) throws IOException
- {
- MutableLong insertKey = layout.key( key );
- MutableLong insertValue = layout.value( value( key ) );
- writer.put( insertKey, insertValue );
- }
-}
diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeFormatTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeFormatTest.java
new file mode 100644
index 0000000000000..a11ad912f1b42
--- /dev/null
+++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeFormatTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2002-2018 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.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 .
+ */
+package org.neo4j.index.internal.gbptree;
+
+import org.apache.commons.lang3.mutable.MutableLong;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.neo4j.cursor.RawCursor;
+import org.neo4j.io.pagecache.IOLimiter;
+import org.neo4j.io.pagecache.PageCache;
+import org.neo4j.test.FormatCompatibilityVerifier;
+import org.neo4j.test.rule.PageCacheRule;
+import org.neo4j.test.rule.RandomRule;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.neo4j.index.internal.gbptree.SimpleLongLayout.longLayout;
+
+@RunWith( Parameterized.class )
+public class GBPTreeFormatTest extends FormatCompatibilityVerifier
+{
+ private static final String STORE = "store";
+ private static final int INITIAL_KEY_COUNT = 10_000;
+ private static final String CURRENT_FIXED_SIZE_FORMAT_ZIP = "current-format.zip";
+ private static final String CURRENT_DYNAMIC_SIZE_FORMAT_ZIP = "current-dynamic-format.zip";
+
+ @Parameters
+ public static List data()
+ {
+ return asList(
+ new Object[] {longLayout().withFixedSize( true ).build(), CURRENT_FIXED_SIZE_FORMAT_ZIP},
+ new Object[] {longLayout().withFixedSize( false ).build(), CURRENT_DYNAMIC_SIZE_FORMAT_ZIP} );
+ }
+
+ @Parameter
+ public SimpleLongLayout layout;
+ @Parameter( 1 )
+ public String zipName;
+
+ private final PageCacheRule pageCacheRule = new PageCacheRule();
+ private final RandomRule random = new RandomRule();
+ private final List initialKeys = initialKeys();
+ private final List keysToAdd = keysToAdd();
+ private List allKeys;
+
+ @Before
+ public void setup()
+ {
+ allKeys = new ArrayList<>();
+ allKeys.addAll( initialKeys );
+ allKeys.addAll( keysToAdd );
+ allKeys.sort( Long::compare );
+ }
+
+ @Rule
+ public final RuleChain chain = RuleChain.outerRule( random ).around( pageCacheRule );
+
+ @Override
+ protected String zipName()
+ {
+ return zipName;
+ }
+
+ @Override
+ protected String storeFileName()
+ {
+ return STORE;
+ }
+
+ @Override
+ protected void createStoreFile( File storeFile ) throws IOException
+ {
+ List initialKeys = initialKeys();
+ PageCache pageCache = pageCacheRule.getPageCache( globalFs.get() );
+ try ( GBPTree tree =
+ new GBPTreeBuilder<>( pageCache, storeFile, layout ).build() )
+ {
+ try ( Writer writer = tree.writer() )
+ {
+ for ( Long key : initialKeys )
+ {
+ put( writer, key );
+ }
+ }
+ tree.checkpoint( IOLimiter.UNLIMITED );
+ }
+ }
+
+ /**
+ * Throws {@link FormatViolationException} if format has changed.
+ */
+ @SuppressWarnings( "EmptyTryBlock" )
+ @Override
+ protected void verifyFormat( File storeFile ) throws IOException, FormatViolationException
+ {
+ PageCache pageCache = pageCacheRule.getPageCache( globalFs.get() );
+ try ( GBPTree ignored =
+ new GBPTreeBuilder<>( pageCache, storeFile, layout ).build() )
+ {
+ }
+ catch ( MetadataMismatchException e )
+ {
+ throw new FormatViolationException( e );
+ }
+ }
+
+ @Override
+ public void verifyContent( File storeFile ) throws IOException
+ {
+ PageCache pageCache = pageCacheRule.getPageCache( globalFs.get() );
+ try ( GBPTree tree =
+ new GBPTreeBuilder<>( pageCache, storeFile, layout ).build() )
+ {
+ {
+ // WHEN reading from the tree
+ // THEN initial keys should be there
+ tree.consistencyCheck();
+ try ( RawCursor,IOException> cursor =
+ tree.seek( layout.key( 0 ), layout.key( Long.MAX_VALUE ) ) )
+ {
+ for ( Long expectedKey : initialKeys )
+ {
+ assertHit( cursor, expectedKey );
+ }
+ assertFalse( cursor.next() );
+ }
+ }
+
+ {
+ // WHEN writing more to the tree
+ // THEN we should not see any format conflicts
+ try ( Writer writer = tree.writer() )
+ {
+ while ( keysToAdd.size() > 0 )
+ {
+ int next = random.nextInt( keysToAdd.size() );
+ put( writer, keysToAdd.get( next ) );
+ keysToAdd.remove( next );
+ }
+ }
+ }
+
+ {
+ // WHEN reading from the tree again
+ // THEN all keys including newly added should be there
+ tree.consistencyCheck();
+ try ( RawCursor,IOException> cursor =
+ tree.seek( layout.key( 0 ), layout.key( 2 * INITIAL_KEY_COUNT ) ) )
+ {
+ for ( Long expectedKey : allKeys )
+ {
+ assertHit( cursor, expectedKey );
+ }
+ assertFalse( cursor.next() );
+ }
+ }
+
+ {
+ // WHEN randomly removing half of tree content
+ // THEN we should not see any format conflicts
+ try ( Writer writer = tree.writer() )
+ {
+ int size = allKeys.size();
+ while ( allKeys.size() > size / 2 )
+ {
+ int next = random.nextInt( allKeys.size() );
+ MutableLong key = layout.key( allKeys.get( next ) );
+ writer.remove( key );
+ allKeys.remove( next );
+ }
+ }
+ }
+
+ {
+ // WHEN reading from the tree after remove
+ // THEN we should see everything that is left in the tree
+ tree.consistencyCheck();
+ try ( RawCursor,IOException> cursor =
+ tree.seek( layout.key( 0 ), layout.key( 2 * INITIAL_KEY_COUNT ) ) )
+ {
+ for ( Long expectedKey : allKeys )
+ {
+ assertHit( cursor, expectedKey );
+ }
+ assertFalse( cursor.next() );
+ }
+ }
+ }
+ }
+
+ private static long value( long key )
+ {
+ return key * 2;
+ }
+
+ private static List initialKeys()
+ {
+ List initialKeys = new ArrayList<>();
+ for ( long i = 0, key = 0; i < INITIAL_KEY_COUNT; i++, key += 2 )
+ {
+ initialKeys.add( key );
+ }
+ return initialKeys;
+ }
+
+ private static List keysToAdd()
+ {
+ List keysToAdd = new ArrayList<>();
+ for ( long i = 0, key = 1; i < INITIAL_KEY_COUNT; i++, key += 2 )
+ {
+ keysToAdd.add( key );
+ }
+ return keysToAdd;
+ }
+
+ private static void assertHit( RawCursor,IOException> cursor, Long expectedKey ) throws IOException
+ {
+ assert cursor.next() : "Had no next when expecting key " + expectedKey;
+ Hit hit = cursor.get();
+ assertEquals( expectedKey.longValue(), hit.key().longValue() );
+ assertEquals( value( expectedKey ), hit.value().longValue() );
+ }
+
+ private void put( Writer writer, long key )
+ {
+ MutableLong insertKey = layout.key( key );
+ MutableLong insertValue = layout.value( value( key ) );
+ writer.put( insertKey, insertValue );
+ }
+}
diff --git a/community/io/src/test/java/org/neo4j/test/FormatCompatibilityVerifier.java b/community/io/src/test/java/org/neo4j/test/FormatCompatibilityVerifier.java
new file mode 100644
index 0000000000000..5cca6f37e20fd
--- /dev/null
+++ b/community/io/src/test/java/org/neo4j/test/FormatCompatibilityVerifier.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2002-2018 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.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 .
+ */
+package org.neo4j.test;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.neo4j.io.compress.ZipUtils;
+import org.neo4j.test.rule.TestDirectory;
+import org.neo4j.test.rule.fs.DefaultFileSystemRule;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * A little trick to automatically tell whether or not index format was changed without
+ * incrementing the format version. This is done by keeping a zipped store file which is opened and tested on.
+ * On failure this test will fail saying that the format version needs update and also update the zipped
+ * store with the new version.
+ */
+public abstract class FormatCompatibilityVerifier
+{
+ private final TestDirectory globalDir = TestDirectory.testDirectory();
+ protected final DefaultFileSystemRule globalFs = new DefaultFileSystemRule();
+
+ @Rule
+ public final RuleChain globalRuleChain = RuleChain.outerRule( globalFs ).around( globalDir );
+
+ @Test
+ public void shouldDetectFormatChange() throws Throwable
+ {
+ File storeFile = globalDir.file( storeFileName() );
+ doShouldDetectFormatChange( zipName(), storeFile );
+ }
+
+ protected abstract String zipName();
+
+ protected abstract String storeFileName();
+
+ protected abstract void createStoreFile( File storeFile ) throws IOException;
+
+ protected abstract void verifyFormat( File storeFile ) throws IOException, FormatViolationException;
+
+ protected abstract void verifyContent( File storeFile ) throws IOException;
+
+ private void doShouldDetectFormatChange( String zipName, File storeFile ) throws Throwable
+ {
+ try
+ {
+ unzip( zipName, storeFile );
+ }
+ catch ( FileNotFoundException e )
+ {
+ // First time this test is run, eh?
+ createStoreFile( storeFile );
+ ZipUtils.zip( globalFs.get(), storeFile, globalDir.file( zipName ) );
+ tellDeveloperToCommitThisFormatVersion( zipName );
+ }
+ assertTrue( zipName + " seems to be missing from resources directory", globalFs.get().fileExists( storeFile ) );
+
+ // Verify format
+ try
+ {
+ verifyFormat( storeFile );
+ }
+ catch ( FormatViolationException e )
+ {
+ // Good actually, or?
+ assertThat( e.getMessage(), containsString( "format version" ) );
+
+ globalFs.get().deleteFile( storeFile );
+ createStoreFile( storeFile );
+ ZipUtils.zip( globalFs.get(), storeFile, globalDir.file( zipName ) );
+
+ tellDeveloperToCommitThisFormatVersion( zipName );
+ }
+
+ // Verify content
+ try
+ {
+ verifyContent( storeFile );
+ }
+ catch ( Throwable t )
+ {
+ throw new AssertionError( "If this is the single failing test in this component then this failure is a strong indication that format " +
+ "has changed without also incrementing TreeNode version(s). Please make necessary format version changes.", t );
+ }
+ }
+
+ private void tellDeveloperToCommitThisFormatVersion( String zipName )
+ {
+ fail( String.format( "This is merely a notification to developer. Format has changed and its version has also " +
+ "been properly incremented. A tree with this new format has been generated and should be committed. " +
+ "Please move:%n %s%ninto %n %s, %nreplacing the existing file there",
+ globalDir.file( zipName ),
+ "" + pathify( ".src.test.resources." ) +
+ pathify( getClass().getPackage().getName() + "." ) + zipName ) );
+ }
+
+ private void unzip( String zipName, File storeFile ) throws IOException
+ {
+ URL resource = getClass().getResource( zipName );
+ if ( resource == null )
+ {
+ throw new FileNotFoundException();
+ }
+
+ try ( ZipFile zipFile = new ZipFile( resource.getFile() ) )
+ {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ assertTrue( entries.hasMoreElements() );
+ ZipEntry entry = entries.nextElement();
+ assertEquals( storeFile.getName(), entry.getName() );
+ Files.copy( zipFile.getInputStream( entry ), storeFile.toPath() );
+ }
+ }
+
+ private static String pathify( String name )
+ {
+ return name.replace( '.', File.separatorChar );
+ }
+
+ public class FormatViolationException extends Throwable
+ {
+ public FormatViolationException( Throwable cause )
+ {
+ super( cause );
+ }
+
+ public FormatViolationException( String message )
+ {
+ super( message );
+ }
+ }
+}
diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateFormatTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateFormatTest.java
new file mode 100644
index 0000000000000..c4decda34edd8
--- /dev/null
+++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateFormatTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2002-2018 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.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 .
+ */
+package org.neo4j.kernel.impl.index.schema;
+
+import org.junit.Before;
+import org.junit.Rule;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.OpenOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import org.neo4j.io.pagecache.PageCache;
+import org.neo4j.io.pagecache.PageCursor;
+import org.neo4j.io.pagecache.PagedFile;
+import org.neo4j.test.FormatCompatibilityVerifier;
+import org.neo4j.test.rule.PageCacheRule;
+import org.neo4j.test.rule.RandomRule;
+import org.neo4j.values.storable.RandomValues;
+import org.neo4j.values.storable.Value;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class GenericKeyStateFormatTest extends FormatCompatibilityVerifier
+{
+ @Rule
+ public PageCacheRule pageCacheRule = new PageCacheRule();
+
+ @Rule
+ public RandomRule randomRule = new RandomRule().withSeedForAllTests( 20051116 );
+
+ private static final int NUMBER_OF_SLOTS = 2;
+ private List values;
+
+ @Before
+ public void setup()
+ {
+ RandomValues rnd = randomRule.randomValues();
+ values = new ArrayList<>();
+ // ZONED_DATE_TIME_ARRAY
+ values.add( rnd.nextDateTimeArray() );
+ // LOCAL_DATE_TIME_ARRAY
+ values.add( rnd.nextLocalDateTimeArray() );
+ // DATE_ARRAY
+ values.add( rnd.nextDateArray() );
+ // ZONED_TIME_ARRAY
+ values.add( rnd.nextTimeArray() );
+ // LOCAL_TIME_ARRAY
+ values.add( rnd.nextLocalTimeArray() );
+ // DURATION_ARRAY
+ values.add( rnd.nextDurationArray() );
+ // TEXT_ARRAY
+ values.add( rnd.nextStringArray() );
+ // BOOLEAN_ARRAY
+ values.add( rnd.nextBooleanArray() );
+ // NUMBER_ARRAY (byte, short, int, long, float, double)
+ values.add( rnd.nextByteArray() );
+ values.add( rnd.nextShortArray() );
+ values.add( rnd.nextIntArray() );
+ values.add( rnd.nextLongArray() );
+ values.add( rnd.nextFloatArray() );
+ values.add( rnd.nextDoubleArray() );
+ // ZONED_DATE_TIME
+ values.add( rnd.nextDateTimeValue() );
+ // LOCAL_DATE_TIME
+ values.add( rnd.nextLocalDateTimeValue() );
+ // DATE
+ values.add( rnd.nextDateValue() );
+ // ZONED_TIME
+ values.add( rnd.nextTimeValue() );
+ // LOCAL_TIME
+ values.add( rnd.nextLocalTimeValue() );
+ // DURATION
+ values.add( rnd.nextDuration() );
+ // TEXT
+ values.add( rnd.nextTextValue() );
+ // BOOLEAN
+ values.add( rnd.nextBooleanValue() );
+ // NUMBER (byte, short, int, long, float, double)
+ values.add( rnd.nextByteValue() );
+ values.add( rnd.nextShortValue() );
+ values.add( rnd.nextIntValue() );
+ values.add( rnd.nextLongValue() );
+ values.add( rnd.nextFloatValue() );
+ values.add( rnd.nextDoubleValue() );
+ // todo GEOMETRY
+ // todo GEOMETRY_ARRAY
+ }
+
+ @Override
+ protected String zipName()
+ {
+ return "current-generic-key-state-format.zip";
+ }
+
+ @Override
+ protected String storeFileName()
+ {
+ return "generic-key-state-store";
+ }
+
+ @Override
+ protected void createStoreFile( File storeFile ) throws IOException
+ {
+ withCursor( storeFile, true, c -> {
+ putFormatVersion( c );
+ putData( c );
+ } );
+ }
+
+ @Override
+ protected void verifyFormat( File storeFile ) throws FormatViolationException, IOException
+ {
+ AtomicReference exception = new AtomicReference<>();
+ withCursor( storeFile, false, c ->
+ {
+ int major = c.getInt();
+ int minor = c.getInt();
+ GenericLayout layout = getLayout();
+ if ( major != layout.majorVersion() || minor != layout.minorVersion() )
+ {
+ exception.set( new FormatViolationException( String.format( "Read format version %d.%d, but layout has version %d.%d",
+ major, minor, layout.majorVersion(), layout.minorVersion() ) ) );
+ }
+ } );
+ if ( exception.get() != null )
+ {
+ throw exception.get();
+ }
+ }
+
+ @Override
+ protected void verifyContent( File storeFile ) throws IOException
+ {
+ withCursor( storeFile, false, c ->
+ {
+ readFormatVersion( c );
+ verifyData( c );
+ } );
+ }
+
+ private void putFormatVersion( PageCursor cursor )
+ {
+ GenericLayout layout = getLayout();
+ int major = layout.majorVersion();
+ cursor.putInt( major );
+ int minor = layout.minorVersion();
+ cursor.putInt( minor );
+ }
+
+ private void readFormatVersion( PageCursor c )
+ {
+ c.getInt(); // Major version
+ c.getInt(); // Minor version
+ }
+
+ private void putData( PageCursor c )
+ {
+ GenericLayout layout = getLayout();
+ CompositeGenericKey key = layout.newKey();
+ for ( Value value : values )
+ {
+ key.initialize( 19570320 );
+ for ( int i = 0; i < NUMBER_OF_SLOTS; i++ )
+ {
+ key.initFromValue( i, value, NativeIndexKey.Inclusion.NEUTRAL );
+ }
+ c.putInt( key.size() );
+ layout.writeKey( c, key );
+ }
+ }
+
+ private void verifyData( PageCursor c )
+ {
+ GenericLayout layout = getLayout();
+ CompositeGenericKey into = layout.newKey();
+ for ( Value value : values )
+ {
+ int keySize = c.getInt();
+ layout.readKey( c, into, keySize );
+ for ( Value readValue : into.asValues() )
+ {
+ assertEquals( value, readValue, "expected read value to be " + value + ", but was " + readValue );
+ }
+ }
+ }
+
+ private GenericLayout getLayout()
+ {
+ return new GenericLayout( NUMBER_OF_SLOTS );
+ }
+
+ private void withCursor( File storeFile, boolean create, Consumer cursorConsumer ) throws IOException
+ {
+ OpenOption[] openOptions = create ?
+ new OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.CREATE} :
+ new OpenOption[]{StandardOpenOption.WRITE};
+ try ( PageCache pageCache = pageCacheRule.getPageCache( globalFs.get() );
+ PagedFile pagedFile = pageCache.map( storeFile, pageCache.pageSize(), openOptions );
+ PageCursor cursor = pagedFile.io( 0, PagedFile.PF_SHARED_WRITE_LOCK ) )
+ {
+ cursor.next();
+ cursorConsumer.accept( cursor );
+ }
+ }
+}
diff --git a/community/kernel/src/test/resources/org/neo4j/kernel/impl/index/schema/current-generic-key-state-format.zip b/community/kernel/src/test/resources/org/neo4j/kernel/impl/index/schema/current-generic-key-state-format.zip
new file mode 100644
index 0000000000000..ee6abe214da70
Binary files /dev/null and b/community/kernel/src/test/resources/org/neo4j/kernel/impl/index/schema/current-generic-key-state-format.zip differ