package com.github.liblevenshtein.serialization; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.UUID; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.testng.SkipException; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.assertj.core.api.Assertions.assertThat; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import com.github.liblevenshtein.collection.dictionary.SortedDawg; import com.github.liblevenshtein.transducer.Algorithm; import com.github.liblevenshtein.transducer.Transducer; import com.github.liblevenshtein.transducer.factory.TransducerBuilder; import static com.github.liblevenshtein.assertion.SetAssertions.assertThat; @Slf4j public class SerializerTest { private static final String DICTIONARY = "dictionary"; private static final String TRANSDUCER = "transducer"; private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); private final Path tmpDir = fs.getPath("/jimfs"); @Getter(lazy = true) private final Object[][] lazySerializerParams = buildParams(); @BeforeTest @SuppressWarnings("checkstyle:illegalcatch") public void setUp() throws IOException { try { Files.createDirectory(tmpDir); } catch (final Throwable thrown) { log.error("Failed to create directory [{}]", tmpDir, thrown); throw thrown; } } @DataProvider(name = "serializerParams") public Object[][] serializerParams() { // I couldn't get @DataProvider to play well with @Getter ... return lazySerializerParams(); } @SuppressWarnings("checkstyle:illegalcatch") private Object[][] buildParams() { try { final AbstractSerializer[] serializers = { new ProtobufSerializer(), new BytecodeSerializer(), new PlainTextSerializer(true), new PlainTextSerializer(false), }; final SortedDawg dictionary = buildDictionary(); final int[] maxDistances = {0, 2, Integer.MAX_VALUE}; final boolean[] includeDistances = {true, false}; final Algorithm[] algorithms = Algorithm.values(); final List<Object[]> provider = new ArrayList<>( serializers.length * maxDistances.length * algorithms.length); for (final AbstractSerializer serializer : serializers) { serializer.fileSystem(fs); for (final int maxDistance : maxDistances) { for (final boolean includeDistance : includeDistances) { for (final Algorithm algorithm : algorithms) { final Transducer<?, ?> transducer = (Transducer<?, ?>) (Object) new TransducerBuilder() .dictionary(dictionary) .algorithm(algorithm) .defaultMaxDistance(maxDistance) .includeDistance(includeDistance) .build(); provider.add(new Object[] { serializer, dictionary, transducer, }); } } } } return provider.toArray(new Object[0][0]); } catch (final Throwable thrown) { final String message = "Failed to build @DataProvider [serializerParams]"; log.error(message, thrown); throw new SkipException(message, thrown); } } @Test(dataProvider = "serializerParams") public void testSerializeBytes( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final byte[] serializedDictionary = serializer.serialize(dictionary); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, serializedDictionary); assertThat(deserializedDictionary).isEqualTo(dictionary); final byte[] serializedTranducer = serializer.serialize(transducer); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, serializedTranducer); assertThat(deserializedTransducer).isEqualTo(transducer); } @Test(dataProvider = "serializerParams") public void testSerializeStream( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final byte[] serializedDictionary; try (final ByteArrayOutputStream stream = new ByteArrayOutputStream()) { serializer.serialize(dictionary, stream); serializedDictionary = stream.toByteArray(); } final SortedDawg deserializedDictionary; try (final InputStream stream = new ByteArrayInputStream(serializedDictionary)) { deserializedDictionary = serializer.deserialize(SortedDawg.class, stream); } assertThat(deserializedDictionary).isEqualTo(dictionary); final byte[] serializedTransducer; try (final ByteArrayOutputStream stream = new ByteArrayOutputStream()) { serializer.serialize(transducer, stream); serializedTransducer = stream.toByteArray(); } final Transducer<?, ?> deserializedTransducer; try (final InputStream stream = new ByteArrayInputStream(serializedTransducer)) { deserializedTransducer = serializer.deserialize(Transducer.class, stream); } assertThat(deserializedTransducer).isEqualTo(transducer); } @Test(dataProvider = "serializerParams") public void testSerializePath( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final Path dictionaryPath = createTempFile(DICTIONARY); try { serializer.serialize(dictionary, dictionaryPath); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, dictionaryPath); assertThat(deserializedDictionary).isEqualTo(dictionary); } finally { Files.delete(dictionaryPath); } final Path transducerPath = createTempFile(TRANSDUCER); try { serializer.serialize(transducer, transducerPath); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, transducerPath); assertThat(deserializedTransducer).isEqualTo(transducer); } finally { Files.delete(transducerPath); } } @Test(dataProvider = "serializerParams") public void testSerializeFile( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final Path dictionaryPath = createTempFile(DICTIONARY); try { final File dictionaryFile = mock(File.class); when(dictionaryFile.toPath()).thenReturn(dictionaryPath); serializer.serialize(dictionary, dictionaryFile); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, dictionaryFile); assertThat(deserializedDictionary).isEqualTo(dictionary); } finally { Files.delete(dictionaryPath); } final Path transducerPath = createTempFile(TRANSDUCER); try { final File transducerFile = mock(File.class); when(transducerFile.toPath()).thenReturn(transducerPath); serializer.serialize(transducer, transducerFile); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, transducerFile); assertThat(deserializedTransducer).isEqualTo(transducer); } finally { Files.delete(transducerPath); } } @Test(dataProvider = "serializerParams") public void testSerializePathString( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final Path dictionaryPath = createTempFile(DICTIONARY); try { final String dictionaryPathString = dictionaryPath.toString(); serializer.serialize(dictionary, dictionaryPathString); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, dictionaryPathString); assertThat(deserializedDictionary).isEqualTo(dictionary); } finally { Files.delete(dictionaryPath); } final Path transducerPath = createTempFile(TRANSDUCER); try { final String transducerPathString = transducerPath.toString(); serializer.serialize(transducer, transducerPathString); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, transducerPathString); assertThat(deserializedTransducer).isEqualTo(transducer); } finally { Files.delete(transducerPath); } } @Test(dataProvider = "serializerParams") public void testDeserializeUri( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final Path dictionaryPath = createTempFile(DICTIONARY); try { final URI dictionaryUri = dictionaryPath.toUri(); serializer.serialize(dictionary, dictionaryPath); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, dictionaryUri); assertThat(deserializedDictionary).isEqualTo(dictionary); } finally { Files.delete(dictionaryPath); } final Path transducerPath = createTempFile(TRANSDUCER); try { final URI transducerUri = transducerPath.toUri(); serializer.serialize(transducer, transducerPath); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, transducerUri); assertThat(deserializedTransducer).isEqualTo(transducer); } finally { Files.delete(transducerPath); } } @Test(dataProvider = "serializerParams") public void testDeserializeUriString( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final Path dictionaryPath = createTempFile(DICTIONARY); try { final URI dictionaryUri = dictionaryPath.toUri(); final String dictionaryUriString = dictionaryUri.toString(); serializer.serialize(dictionary, dictionaryPath); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, dictionaryUriString); assertThat(deserializedDictionary).isEqualTo(dictionary); } finally { Files.delete(dictionaryPath); } final Path transducerPath = createTempFile(TRANSDUCER); try { final URI transducerUri = transducerPath.toUri(); final String transducerUriString = transducerUri.toString(); serializer.serialize(transducer, transducerPath); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, transducerUriString); assertThat(deserializedTransducer).isEqualTo(transducer); } finally { Files.delete(transducerPath); } } @Test(dataProvider = "serializerParams") public void testDeserializeUrl( final Serializer serializer, final SortedDawg dictionary, final Transducer<?, ?> transducer) throws Exception { final String protocol = "http"; final String domain = "www.example.com"; final byte[] serializedDictionary = serializer.serialize(dictionary); try (final InputStream stream = new ByteArrayInputStream(serializedDictionary)) { final URLConnection connection = mock(URLConnection.class); when(connection.getInputStream()).thenReturn(stream); final URLStreamHandler handler = new URLStreamHandler() { @Override protected URLConnection openConnection(final URL url) { return connection; } }; final URL url = new URL(protocol, domain, 80, "/dictionary.tmp", handler); final SortedDawg deserializedDictionary = serializer.deserialize(SortedDawg.class, url); assertThat(deserializedDictionary).isEqualTo(dictionary); } final byte[] serializedTransducer = serializer.serialize(transducer); try (final InputStream stream = new ByteArrayInputStream(serializedTransducer)) { final URLConnection connection = mock(URLConnection.class); when(connection.getInputStream()).thenReturn(stream); final URLStreamHandler handler = new URLStreamHandler() { @Override protected URLConnection openConnection(final URL url) { return connection; } }; final URL url = new URL(protocol, domain, 80, "/transducer.tmp", handler); final Transducer<?, ?> deserializedTransducer = serializer.deserialize(Transducer.class, url); assertThat(deserializedTransducer).isEqualTo(transducer); } } private Path createTempFile(final String type) throws IOException { final Path tempFile = tmpDir.resolve(String.format("%s-%s.tmp", type, UUID.randomUUID())); Files.createFile(tempFile); return tempFile; } private SortedDawg buildDictionary() throws Exception { final URL dictionaryUrl = getClass() .getResource("/top-20-most-common-english-words.protobuf.bytes"); final Serializer serializer = new ProtobufSerializer(); return serializer.deserialize(SortedDawg.class, dictionaryUrl); } }