/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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.novelang.build.unicode; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Map; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import org.junit.Test; import static org.junit.Assert.assertEquals; import org.novelang.logger.Logger; import org.novelang.logger.LoggerFactory; /** * Tests for {@link org.novelang.build.unicode.UnicodeNamesGenerator}. * * @author Laurent Caillette */ public class UnicodeNamesGeneratorTest { @Test public void convertIntegerToBytes() { for( final Map.Entry< Integer, byte[] > entry : INTEGER_TO_BYTES.entrySet() ) { final byte[] expectedBytes = entry.getValue(); final String message = "Int: " + String.format( "%6d", entry.getKey() ) + " bytes: [ " + toHexadecimalString( expectedBytes ) + " ]" ; final byte[] actualBytes = UnicodeNamesGenerator.asBytes( entry.getKey() ) ; assertEquals( message, expectedBytes.length, actualBytes.length ) ; for( int i = 0 ; i < expectedBytes.length ; i ++ ) { assertEquals( message, expectedBytes[ i ], actualBytes[ i ] ) ; } LOGGER.info( message ) ; } } @Test public void twoContiguousValues() throws IOException { final Map< Character, String > map = new ImmutableMap.Builder< Character, String >() .put( '\u0000', "Zero" ) // 90 101 114 11 0 (total length 5) .put( '\u0001', "One" ) // 79 110 101 0 (total length 4) .build() ; final ByteArrayOutputStream bytes = new ByteArrayOutputStream() ; UnicodeNamesGenerator.generate( bytes, map, 2 ) ; final byte[] actualBytes = bytes.toByteArray() ; final byte[] expectedBytes = { 0, 0, 0, 2, // Size of the offset table. 0, 0, 0, 12, // Offset to "Zero". 0, 0, 0, 17, // Offset to "One". 90, 101, 114, 111, 0, // Z e r o \0 79, 110, 101, 0 // O n e \0 } ; final String message = "Expected: \n[ " + toHexadecimalString( expectedBytes ) + " ] " + "but got \n[ " + toHexadecimalString( actualBytes ) + " ]\n" ; assertEquals( message, expectedBytes.length, actualBytes.length ) ; for( int i = 0 ; i < expectedBytes.length ; i ++ ) { assertEquals( message, expectedBytes[ i ], actualBytes[ i ] ) ; } } @Test public void generateLastFffd() throws IOException { final int lastCharacterIndex = 0xfffd; final Map< Character, String > map = new ImmutableMap.Builder< Character, String >() .put( ( char ) lastCharacterIndex, "Last" ) // 90 101 114 11 0 (total length 5) .build() ; final ByteArrayOutputStream bytes = new ByteArrayOutputStream() ; UnicodeNamesGenerator.generate( bytes, map, lastCharacterIndex + 1 ) ; final byte[] actualBytes = bytes.toByteArray() ; final byte[] expectedBytesForCharacterCount = UnicodeNamesGenerator.asBytes( lastCharacterIndex + 1 ) ; assertEquals( expectedBytesForCharacterCount[ 0 ], actualBytes[ 0 ] ) ; assertEquals( expectedBytesForCharacterCount[ 1 ], actualBytes[ 1 ] ) ; assertEquals( expectedBytesForCharacterCount[ 2 ], actualBytes[ 2 ] ) ; assertEquals( expectedBytesForCharacterCount[ 3 ], actualBytes[ 3 ] ) ; final int totalBytes = 4 + // Character count at start. ( ( lastCharacterIndex + 1 ) * 4 ) + // Offsets 5 // L a s t \0 ; assertEquals( totalBytes, actualBytes.length ) ; final byte[] expectedBytesForOffset = UnicodeNamesGenerator.asBytes( 4 + // Character count. lastCharacterIndex * 4 + // Offset table. 4 // Size of the offset itself. ) ; final byte[] expectedBytesForName = { 76 /*0x4C*/, 97 /*0x4A*/, 115 /*0x74*/, 116 /*0x74*/, 0 } ; // Dump last actual bytes. final byte[] lastOfActualBytes = new byte[ expectedBytesForOffset.length + expectedBytesForName.length ] ; System.arraycopy( actualBytes, actualBytes.length - lastOfActualBytes.length, lastOfActualBytes, 0, lastOfActualBytes.length ) ; final String expectedBytesAsString = toHexadecimalString( expectedBytesForOffset ) + " " + toHexadecimalString( expectedBytesForName ); final String actualBytesAsString = toHexadecimalString( lastOfActualBytes ); LOGGER.info( "\nLast expected bytes (offset + name) vs ", "last ", lastOfActualBytes.length, " actual bytes ", "(starting at ", ( actualBytes.length - lastOfActualBytes.length ), "): \n", expectedBytesAsString, "\n", actualBytesAsString ) ; assertEquals( expectedBytesAsString, actualBytesAsString ) ; } @Test public void twoValuesWithAHoleInTheMiddle() throws IOException { final Map< Character, String > map = new ImmutableMap.Builder< Character, String >() .put( '\u0000', "Zero" ) // 90 101 114 111 0 (total length 5) // Discontinuity, no \u0001 value here. .put( '\u0002', "Two" ) // 84 119 111 0 (total length 3) .build() ; final ByteArrayOutputStream bytes = new ByteArrayOutputStream() ; UnicodeNamesGenerator.generate( bytes, map, 4 ) ; final byte[] actualBytes = bytes.toByteArray() ; final byte[] expectedBytes = { 0, 0, 0, 4, // 3 characters. 0, 0, 0, 20, // Offset to "Zero". 0, 0, 0, 0, // Offset for some undefined character. 0, 0, 0, 25, // Offset to "One". 0, 0, 0, 0, // Offset for some undefined character. 90, 101, 114, 111, 0, // Z e r o \0 84, 119, 111, 0 // T w o \0 } ; final String message = "Expected: \n[ " + toHexadecimalString( expectedBytes ) + " ] " + "but got \n[ " + toHexadecimalString( actualBytes ) + " ]\n" ; assertEquals( message, expectedBytes.length, actualBytes.length ) ; for( int i = 0 ; i < expectedBytes.length ; i ++ ) { assertEquals( message, expectedBytes[ i ], actualBytes[ i ] ) ; } } // ======= // Fixture // ======= private static final Logger LOGGER = LoggerFactory.getLogger( UnicodeNamesGeneratorTest.class ); private static final BiMap< Integer, byte[] > INTEGER_TO_BYTES = new ImmutableBiMap.Builder< Integer, byte[] >() .put( 0, new byte[] { 0, 0, 0, 0 } ) .put( 1, new byte[] { 0, 0, 0, 1 } ) .put( 10, new byte[] { 0, 0, 0, 10 } ) .put( 255, new byte[] { 0, 0, 0, -1 } ) .put( 65535, new byte[] { 0, 0, -1, -1 } ) .build() ; private static String toHexadecimalString( final byte[] bytes ) { final StringBuilder builder = new StringBuilder() ; for( final byte b : bytes ) { final int unsignedByteVal = toUnsignedInt( b ) ; builder.append( " " ) ; final String formattedHexString = unsignedByteTo2DigitHex( unsignedByteVal ) ; builder.append( formattedHexString ) ; } return builder.length() > 0 ? builder.toString().substring( 1 ) : "" ; } private static String unsignedByteTo2DigitHex( final int unsignedByteVal) { final String hexString = Integer.toHexString( unsignedByteVal ) ; return hexString.length() > 1 ? hexString : "0" + hexString ; } private static int toUnsignedInt( final byte b ) { // There must be a better thing to do. final ByteBuffer byteBuffer = ByteBuffer.allocate( 4 ) ; byteBuffer.put( new byte[] { 0, 0, 0, b } ) ; byteBuffer.rewind() ; return byteBuffer.getInt() ; } }