package io.kaif.flake;
import static org.junit.Assert.*;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Test;
import com.google.common.collect.Sets;
public class FlakeIdGeneratorTest {
static class DummyFlakeGenerator extends FlakeIdGenerator {
protected DummyFlakeGenerator(int nodeId, Clock clock, String scope) {
super(nodeId, clock, scope);
}
//this is like production
private DummyFlakeGenerator() {
super(445);
}
}
private Clock fixed(int month, int day) {
Instant now = LocalDate.of(2015, month, day).atStartOfDay(ZoneOffset.UTC).toInstant();
return Clock.fixed(now, ZoneOffset.UTC);
}
@Test
public void generate() throws Exception {
DummyFlakeGenerator gen12 = new DummyFlakeGenerator(12, fixed(1, 2), "generate");
assertEquals(12, gen12.getNodeId());
FlakeId flakeId = gen12.next();
//2015/01/02 UTC
assertEquals(1420156800000L, flakeId.epochMilli());
assertEquals(353894400000L, flakeId.sequenceTime());
assertEquals(362387865600012L, flakeId.value());
assertEquals(12, flakeId.nodeId());
}
@Test
public void generatorWithDifferentTime() throws Exception {
FlakeId id3 = new DummyFlakeGenerator(1, fixed(1, 3), "gen3").next();
assertEquals(1420243200000L, id3.epochMilli());
assertEquals(724775731200001L, id3.value());
FlakeId id4 = new DummyFlakeGenerator(1, fixed(1, 4), "gen4").next();
assertEquals(1420329600000L, id4.epochMilli());
assertEquals(1087163596800001L, id4.value());
}
@Test
public void generateWithSubMilliSecond() throws Exception {
DummyFlakeGenerator generator = new DummyFlakeGenerator(1, fixed(10, 3), "genSub");
List<FlakeId> flakeIds = IntStream.range(0, 4096)
.mapToObj(i -> generator.next())
.collect(Collectors.toList());
assertEquals("flake id generated on the same time should different in sub millisecond",
4096,
Sets.newHashSet(flakeIds).size());
assertEquals(99656663040000001L, flakeIds.get(0).value());
assertEquals(99656663044193281L, flakeIds.get(4095).value());
}
@Test
public void generateExceedSubMilliSecond() throws Exception {
DummyFlakeGenerator production = new DummyFlakeGenerator();
Set<FlakeId> flakeIds = IntStream.range(0, 1024000).parallel() //testing multi-thread
.mapToObj(i -> production.next()).collect(Collectors.toSet());
assertEquals(1024000, flakeIds.size());
assertEquals(445, flakeIds.stream().findFirst().get().nodeId());
}
@Test
public void generateWithOrder() throws Exception {
DummyFlakeGenerator production = new DummyFlakeGenerator();
List<FlakeId> flakeIds = IntStream.range(0, 10240)
.mapToObj(i -> production.next())
.collect(Collectors.toList());
assertEquals(10240, flakeIds.size());
ArrayList<FlakeId> oldOrders = new ArrayList<>(flakeIds);
Collections.shuffle(flakeIds);
Collections.sort(flakeIds);
assertEquals(oldOrders, flakeIds);
}
@Test
public void generatorWithDifferentNodeId() throws Exception {
DummyFlakeGenerator gen1001 = new DummyFlakeGenerator(1001, fixed(1, 2), "gen2");
assertEquals(1001, gen1001.getNodeId());
FlakeId flakeId = gen1001.next();
//2015/01/02 UTC
assertEquals(1420156800000L, flakeId.epochMilli());
assertEquals(362387865601001L, flakeId.value());
}
@Test
public void base62() throws Exception {
DummyFlakeGenerator generator = new DummyFlakeGenerator(1001, fixed(4, 3), "gen43");
FlakeId flakeId = generator.next();
assertEquals("cCRjZpXdiL", flakeId.toString());
assertEquals(flakeId, FlakeId.fromString("cCRjZpXdiL"));
FlakeId next = generator.next();
assertEquals("cCRjZpXdzh", next.toString());
assertEquals(next, FlakeId.fromString("cCRjZpXdzh"));
}
@Test
public void startOf() throws Exception {
assertEquals(0L, FlakeId.MIN.value());
Instant now = LocalDate.of(2016, 10, 11).atStartOfDay(ZoneOffset.UTC).toInstant();
FlakeId start = FlakeId.startOf(now.toEpochMilli());
assertEquals(0, start.nodeId());
assertEquals(235189724774400000L, start.value());
assertEquals(229677465600000L, start.sequenceTime());
//not allow epoch milli before 2015
long before2015 = 1420000000000L;
try {
FlakeId.startOf(before2015);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void endOf() throws Exception {
assertEquals(Long.MAX_VALUE, FlakeId.MAX.value());
Instant now = LocalDate.of(2050, 8, 31).atStartOfDay(ZoneOffset.UTC).toInstant();
FlakeId end = FlakeId.endOf(now.toEpochMilli());
assertEquals(1023, end.nodeId());
assertEquals(4720464337309794303L, end.value());
assertEquals(4609828454404095L, end.sequenceTime());
//not allow epoch milli before 2015
long before2015 = 1420000000000L;
try {
FlakeId.endOf(before2015);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
}
}
}