ulid-creator/src/test/java/com/github/f4b6a3/ulid/UlidTest.java

552 lines
16 KiB
Java

package com.github.f4b6a3.ulid;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Random;
import java.util.UUID;
import org.junit.Test;
import com.github.f4b6a3.ulid.Ulid;
public class UlidTest {
private static final int DEFAULT_LOOP_MAX = 1_000;
protected static final long TIME_MASK = 0x0000ffffffffffffL;
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
private static final long VERSION_MASK = 0x000000000000f000L;
private static final long VARIANT_MASK = 0xc000000000000000L;
@Test
public void testConstructorLongs() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
Ulid ulid0 = new Ulid(msb, lsb); // <-- test Ulid(long, long)
assertEquals(msb, ulid0.getMostSignificantBits());
assertEquals(lsb, ulid0.getLeastSignificantBits());
}
}
@Test
public void testConstructorTimeAndRandom() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
// get the time
long time = msb >>> 16;
// get the random bytes
ByteBuffer buffer = ByteBuffer.allocate(Ulid.RANDOM_BYTES_LENGTH);
buffer.put((byte) ((msb >>> 8) & 0xff));
buffer.put((byte) (msb & 0xff));
buffer.putLong(lsb);
byte[] bytes = buffer.array();
Ulid ulid0 = new Ulid(time, bytes); // <-- test Ulid(long, byte[])
assertEquals(msb, ulid0.getMostSignificantBits());
assertEquals(lsb, ulid0.getLeastSignificantBits());
}
try {
long time = 0x0000ffffffffffffL + 1; // greater than 2^48-1
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH];
new Ulid(time, bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
try {
long time = 0x1000000000000000L; // negative number
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH];
new Ulid(time, bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
try {
long time = 0x0000000000000000L;
byte[] bytes = null; // null random component
new Ulid(time, bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
try {
long time = 0x0000000000000000L;
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH + 1]; // random component with invalid size
new Ulid(time, bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
}
@Test
public void testFromStrings() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
Ulid ulid0 = new Ulid(msb, lsb);
String string0 = toString(ulid0);
Ulid ulid1 = Ulid.from(string0); // <- test Ulid.from(String)
assertEquals(ulid0, ulid1);
}
}
@Test
public void testToString() {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID uuid0 = UUID.randomUUID();
String string0 = toString(uuid0);
String string1 = Ulid.from(uuid0).toString(); // <- test Ulid.toString()
assertEquals(string0, string1);
}
}
@Test
public void testToUpperCase() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
Ulid ulid0 = new Ulid(msb, lsb);
String string1 = toString(ulid0);
String string2 = ulid0.toUpperCase(); // <- test Ulid.toUpperCase()
assertEquals(string1, string2);
// RFC-4122 UUID v4
UUID uuid0 = new UUID(msb, lsb);
String string3 = ulid0.toRfc4122().toUpperCase(); // <- test Ulid.toRfc4122().toUpperCase()
Ulid ulid3 = fromString(string3);
UUID uuid3 = new UUID(ulid3.getMostSignificantBits(), ulid3.getLeastSignificantBits());
assertEquals(4, uuid3.version()); // check version
assertEquals(2, uuid3.variant()); // check variant
assertEquals(uuid0.getMostSignificantBits() & ~VERSION_MASK,
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
assertEquals(uuid0.getLeastSignificantBits() & ~VARIANT_MASK,
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
}
}
@Test
public void testToLowerCase() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
Ulid ulid0 = new Ulid(msb, lsb);
String string1 = toString(ulid0).toLowerCase();
String string2 = ulid0.toLowerCase(); // <- test Ulid.toLowerCase()
assertEquals(string1, string2);
// RFC-4122 UUID v4
UUID uuid0 = new UUID(msb, lsb);
String string3 = ulid0.toRfc4122().toLowerCase(); // <- test Ulid.toRfc4122().toLowerCase()
Ulid ulid3 = fromString(string3);
UUID uuid3 = new UUID(ulid3.getMostSignificantBits(), ulid3.getLeastSignificantBits());
assertEquals(4, uuid3.version()); // check version
assertEquals(2, uuid3.variant()); // check variant
assertEquals(uuid0.getMostSignificantBits() & ~VERSION_MASK,
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
assertEquals(uuid0.getLeastSignificantBits() & ~VARIANT_MASK,
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
}
}
@Test
public void testFromUUID() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long msb = random.nextLong();
final long lsb = random.nextLong();
UUID uuid0 = new UUID(msb, lsb);
Ulid ulid0 = Ulid.from(uuid0); // <- test Ulid.from(UUID)
assertEquals(uuid0.getMostSignificantBits(), ulid0.getMostSignificantBits());
assertEquals(uuid0.getLeastSignificantBits(), ulid0.getLeastSignificantBits());
}
}
@Test
public void testToUuid() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
final long random1 = random.nextLong();
final long random2 = random.nextLong();
Ulid ulid0 = new Ulid(random1, random2);
UUID uuid1 = toUuid(ulid0);
UUID uuid2 = ulid0.toUuid(); // <-- test Ulid.toUuid()
assertEquals(uuid1, uuid2);
// RFC-4122 UUID v4
UUID uuid3 = ulid0.toRfc4122().toUuid(); // <-- test Ulid.toRfc4122().toUuid()
assertEquals(4, uuid3.version()); // check version
assertEquals(2, uuid3.variant()); // check variant
assertEquals(uuid1.getMostSignificantBits() & ~VERSION_MASK,
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
assertEquals(uuid1.getLeastSignificantBits() & ~VARIANT_MASK,
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
}
}
@Test
public void testFromBytes() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
byte[] bytes0 = new byte[Ulid.ULID_BYTES_LENGTH];
random.nextBytes(bytes0);
Ulid ulid0 = Ulid.from(bytes0); // <- test Ulid.from(UUID)
ByteBuffer buffer = ByteBuffer.allocate(Ulid.ULID_BYTES_LENGTH);
buffer.putLong(ulid0.getMostSignificantBits());
buffer.putLong(ulid0.getLeastSignificantBits());
byte[] bytes1 = buffer.array();
for (int j = 0; j < bytes0.length; j++) {
assertEquals(bytes0[j], bytes1[j]);
}
}
try {
byte[] bytes = null;
Ulid.from(bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
try {
byte[] bytes = new byte[Ulid.ULID_BYTES_LENGTH + 1];
Ulid.from(bytes);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
}
@Test
public void testToBytes() {
Random random = new Random();
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
byte[] bytes1 = new byte[16];
random.nextBytes(bytes1);
Ulid ulid0 = Ulid.from(bytes1);
byte[] bytes2 = ulid0.toBytes(); // <-- test Ulid.toBytes()
for (int j = 0; j < bytes1.length; j++) {
assertEquals(bytes1[j], bytes2[j]);
}
// RFC-4122 UUID v4
byte[] bytes3 = ulid0.toRfc4122().toBytes(); // <-- test Ulid.toBytes4()
assertEquals(0x40, bytes3[6] & 0b11110000); // check version
assertEquals(bytes1[6] & 0b00001111, bytes3[6] & 0b00001111); // check the other bits of 7th byte
assertEquals(0x80, bytes3[8] & 0b11000000); // check variant
assertEquals(bytes1[8] & 0b00111111, bytes3[8] & 0b00111111); // check the other bits of 9th byte
for (int j = 0; j < bytes1.length; j++) {
if (j == 6 || j == 8)
continue;
assertEquals(bytes1[j], bytes3[j]); // check the other bytes
}
}
}
@Test
public void testGetTimeAndGetRandom() {
long time = 0;
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH];
Random random = new Random();
for (int i = 0; i < 100; i++) {
time = random.nextLong() & TIME_MASK;
random.nextBytes(bytes);
// Instance methods
Ulid ulid = new Ulid(time, bytes);
assertEquals(time, ulid.getTime()); // test Ulid.getTime()
assertEquals(Instant.ofEpochMilli(time), ulid.getInstant()); // test Ulid.getInstant()
for (int j = 0; j < bytes.length; j++) {
assertEquals(bytes[j], ulid.getRandom()[j]); // test Ulid.getRandom()
}
// Static methods
String string = new Ulid(time, bytes).toString();
assertEquals(time, Ulid.getTime(string)); // test Ulid.getTime()
assertEquals(Instant.ofEpochMilli(time), Ulid.getInstant(string)); // test Ulid.getInstant()
for (int j = 0; j < bytes.length; j++) {
assertEquals(bytes[j], Ulid.getRandom(string)[j]); // test Ulid.getRandom()
}
}
}
@Test
public void testIncrement() {
long msb;
long lsb;
Ulid ulid;
final int loopMax = 100;
msb = 0x0123456789abcdefL;
lsb = 0x0123456789abcdefL;
ulid = new Ulid(msb, lsb);
for (int i = 0; i < loopMax; i++) {
ulid = ulid.increment();
}
assertEquals(msb, ulid.getMostSignificantBits());
assertEquals(msb + loopMax, ulid.getLeastSignificantBits());
msb = 0x0123456789abcdefL;
lsb = 0xffffffffffffffffL - (loopMax / 2);
ulid = new Ulid(msb, lsb);
for (int i = 0; i < loopMax; i++) {
ulid = ulid.increment();
}
assertEquals(msb + 1, ulid.getMostSignificantBits());
assertEquals((loopMax / 2) - 1, ulid.getLeastSignificantBits());
}
@Test
public void testIsValidString() {
String ulid = null; // Null
assertFalse("Null ULID should be invalid.", Ulid.isValid(ulid));
ulid = ""; // length: 0
assertFalse("ULID with empty string should be invalid .", Ulid.isValid(ulid));
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
assertTrue("ULID in upper case should valid.", Ulid.isValid(ulid));
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
assertTrue("ULID in lower case should be valid.", Ulid.isValid(ulid));
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
assertTrue("Ulid in upper and lower case should valid.", Ulid.isValid(ulid));
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
assertFalse("ULID length lower than 26 should be invalid.", Ulid.isValid(ulid));
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
assertFalse("ULID length greater than 26 should be invalid.", Ulid.isValid(ulid));
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
assertFalse("ULID with 'u' or 'U' should be invalid.", Ulid.isValid(ulid));
ulid = "0123456789ABCDEFGHJKMNPQR#"; // Special char
assertFalse("ULID with special chars should be invalid.", Ulid.isValid(ulid));
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // time > (2^48)-1
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", Ulid.isValid(ulid));
}
@Test
public void testToCharArray() {
String ulid = null; // Null
try {
Ulid.toCharArray(ulid);
fail("Null ULID should be invalid.");
} catch (IllegalArgumentException e) {
// success
}
ulid = ""; // length: 0
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
try {
Ulid.toCharArray(ulid);
} catch (IllegalArgumentException e) {
fail("Should not throw an exception");
}
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
try {
Ulid.toCharArray(ulid);
} catch (IllegalArgumentException e) {
fail("Should not throw an exception");
}
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
try {
Ulid.toCharArray(ulid);
} catch (IllegalArgumentException e) {
fail("Should not throw an exception");
}
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
ulid = "0123456789ABCDEFGHJKMNPQR@"; // Special char
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // time > (2^48)-1
try {
Ulid.toCharArray(ulid);
fail("Should throw an exception");
} catch (IllegalArgumentException e) {
// success
}
}
public static Ulid fromString(String string) {
long time = 0;
long random1 = 0;
long random2 = 0;
String tm = string.substring(0, 10).toUpperCase();
String r1 = string.substring(10, 18).toUpperCase();
String r2 = string.substring(18, 26).toUpperCase();
tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA);
r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA);
r2 = transliterate(r2, ALPHABET_CROCKFORD, ALPHABET_JAVA);
time = Long.parseUnsignedLong(tm, 32);
random1 = Long.parseUnsignedLong(r1, 32);
random2 = Long.parseUnsignedLong(r2, 32);
long msb = (time << 16) | (random1 >>> 24);
long lsb = (random1 << 40) | (random2 & 0xffffffffffL);
return new Ulid(msb, lsb);
}
public static UUID toUuid(Ulid struct) {
long msb = struct.toUuid().getMostSignificantBits();
long lsb = struct.toUuid().getLeastSignificantBits();
return new UUID(msb, lsb);
}
public static UUID toUuid(final long time, final long random1, final long random2) {
long tm = time & 0xffffffffffffL;
long r1 = random1 & 0xffffffffffL;
long r2 = random2 & 0xffffffffffL;
final long msb = (tm << 16) | (r1 >>> 24);
final long lsb = (r1 << 40) | r2;
return new UUID(msb, lsb);
}
public static String toString(Ulid ulid) {
return toString(ulid.toUuid().getMostSignificantBits(), ulid.toUuid().getLeastSignificantBits());
}
public static String toString(UUID uuid) {
final long msb = uuid.getMostSignificantBits();
final long lsb = uuid.getLeastSignificantBits();
return toString(msb, lsb);
}
public static String toString(final long msb, final long lsb) {
String timeComponent = toTimeComponent(msb >>> 16);
String randomComponent = toRandomComponent(msb, lsb);
return timeComponent + randomComponent;
}
public static String toTimeComponent(final long time) {
final String tzero = "0000000000";
String tm = Long.toUnsignedString(time, 32);
tm = tzero.substring(0, tzero.length() - tm.length()) + tm;
return transliterate(tm, ALPHABET_JAVA, ALPHABET_CROCKFORD);
}
public static String toRandomComponent(final long msb, final long lsb) {
final String zeros = "00000000";
final long random1 = ((msb & 0xffffL) << 24) | (lsb >>> 40);
final long random2 = (lsb & 0xffffffffffL);
String r1 = Long.toUnsignedString(random1, 32);
String r2 = Long.toUnsignedString(random2, 32);
r1 = zeros.substring(0, zeros.length() - r1.length()) + r1;
r2 = zeros.substring(0, zeros.length() - r2.length()) + r2;
r1 = transliterate(r1, ALPHABET_JAVA, ALPHABET_CROCKFORD);
r2 = transliterate(r2, ALPHABET_JAVA, ALPHABET_CROCKFORD);
return r1 + r2;
}
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
char[] output = string.toCharArray();
for (int i = 0; i < output.length; i++) {
for (int j = 0; j < alphabet1.length; j++) {
if (output[i] == alphabet1[j]) {
output[i] = alphabet2[j];
break;
}
}
}
return new String(output);
}
}