@Test
public void testSingle() {
    SimpleStack<Object> q = new SimpleStack<>();
    Object o1 = new Object();
    Assert.assertTrue(q.isEmpty());
    q.push(o1);
    Assert.assertFalse(q.isEmpty());
    Object r1 = q.pop();
    Assert.assertEquals(o1, r1);
    Assert.assertTrue(q.isEmpty());
}

public boolean isEmpty() {
    return head == -1;
}

@Test
public void testOrder() {
    SimpleStack<Object> q = new SimpleStack<>();
    Object o1 = new Object();
    Object o2 = new Object();
    Assert.assertTrue(q.isEmpty());
    q.push(o1);
    q.push(o2);
    Assert.assertFalse(q.isEmpty());
    Object r2 = q.pop();
    Object r1 = q.pop();
    Assert.assertEquals(o1, r1);
    Assert.assertEquals(o2, r2);
    Assert.assertTrue(q.isEmpty());
}

public E pop() {
    Object e = elements[head];
    elements[head] = null;
    head--;
    return (E) e;
}

@Test
public void testGrow() {
    SimpleStack<Object> q = new SimpleStack<>();
    Object o1 = new Object();
    Object o2 = new Object();
    Object o3 = new Object();
    Assert.assertTrue(q.isEmpty());
    q.push(o1);
    q.push(o2);
    q.push(o3);
    Assert.assertFalse(q.isEmpty());
    Object r3 = q.pop();
    Object r2 = q.pop();
    Object r1 = q.pop();
    Assert.assertEquals(o1, r1);
    Assert.assertEquals(o2, r2);
    Assert.assertEquals(o3, r3);
    Assert.assertTrue(q.isEmpty());
}


@Test
public void testNull() throws Exception {
    File file = File.createTempFile("jol", "imagetest");
    try {
        GraphLayout.parseInstance(null).toImage(file.getAbsolutePath());
        Assert.fail();
    } catch (IllegalArgumentException e) {
    }
}

public static GraphLayout parseInstance(Object... roots) {
    GraphLayout data = new GraphLayout(roots);
    GraphWalker walker = new GraphWalker(roots);
    walker.addVisitor(data.visitor());
    walker.walk();
    return data;
}

public static GraphLayout parseInstance(Object... roots) {
    if (roots == null) {
        throw new IllegalArgumentException("Roots are null");
    }
    for (Object root : roots) {
        if (root == null) {
            throw new IllegalArgumentException("Some root is null");
        }
    }
    GraphLayout data = new GraphLayout(roots);
    GraphWalker walker = new GraphWalker(roots);
    walker.addVisitor(data.visitor());
    walker.walk();
    return data;
}

@Test
public void testNullVararg() throws Exception {
    Object obj = new Object();
    File file = File.createTempFile("jol", "imagetest");
    try {
        GraphLayout.parseInstance(obj, null).toImage(file.getAbsolutePath());
        Assert.fail();
    } catch (IllegalArgumentException e) {
    }
}

@Test
public void testSingleObject() throws Exception {
    Object obj = new Object();
    File file = File.createTempFile("jol", "imagetest");
    GraphLayout.parseInstance(obj).toImage(file.getAbsolutePath());
    Assert.assertTrue(file.exists());
    Assert.assertTrue(file.length() > 0);
}

public void toImage(String fileName) throws IOException {
    if (addresses().isEmpty())
        return;
    long start = startAddress();
    long end = endAddress();
    final int WIDTH = 1000;
    final int HEIGHT = 320;
    final int GRAPH_HEIGHT = 100;
    final int SCALE_WIDTH = 30;
    final int EXT_PAD = 50;
    final int PAD = 20;
    BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = image.createGraphics();
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, WIDTH, HEIGHT);
    int minDepth = Integer.MAX_VALUE;
    int maxDepth = Integer.MIN_VALUE;
    for (long addr : addresses()) {
        GraphPathRecord p = record(addr);
        minDepth = Math.min(minDepth, p.depth());
        maxDepth = Math.max(maxDepth, p.depth());
    }
    Multiset<Integer> depths = new Multiset<Integer>();
    for (long addr : addresses()) {
        GraphPathRecord r = record(addr);
        depths.add(r.depth(), r.size());
    }
    int lastX = 0;
    for (long addr : addresses()) {
        long size = record(addr).size();
        int x1 = SCALE_WIDTH + EXT_PAD + (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * (addr - start) / (end - start));
        int x2 = SCALE_WIDTH + EXT_PAD + (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * (addr + size - start) / (end - start));
        x1 = Math.max(x1, lastX);
        x2 = Math.max(x2, lastX);
        float relDepth = 1.0f * (record(addr).depth() - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        g.fillRect(x1, EXT_PAD, x2 - x1, GRAPH_HEIGHT);
    }
    for (int depth = minDepth; depth <= maxDepth; depth++) {
        float relDepth = 1.0f * (depth - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        int y1 = HEIGHT * (depth - minDepth) / (maxDepth - minDepth + 1);
        int y2 = HEIGHT * (depth + 1 - minDepth) / (maxDepth - minDepth + 1);
        g.fillRect(0, y1, SCALE_WIDTH, y2 - y1);
    }
    lastX = SCALE_WIDTH + EXT_PAD;
    for (int depth = minDepth; depth <= maxDepth; depth++) {
        int w = (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * depths.count(depth) / (end - start));
        float relDepth = 1.0f * (depth - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        g.fillRect(lastX, GRAPH_HEIGHT + EXT_PAD + PAD, w, GRAPH_HEIGHT);
        lastX += w;
    }
    g.setColor(Color.BLACK);
    g.setStroke(new BasicStroke(2.0f));
    g.drawRect(SCALE_WIDTH + EXT_PAD, EXT_PAD, WIDTH - EXT_PAD * 2 - SCALE_WIDTH, GRAPH_HEIGHT);
    g.drawRect(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT + EXT_PAD + PAD, WIDTH - EXT_PAD * 2 - SCALE_WIDTH, GRAPH_HEIGHT);
    g.setStroke(new BasicStroke(1.0f));
    g.drawLine(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD, WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD);
    g.drawLine(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD - 5, SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD + 5);
    g.drawLine(WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD - 5, WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD + 5);
    Font font = new Font("Serif", Font.PLAIN, 18);
    g.setFont(font);
    String labelDense = (end - start) / 1024 + " Kb";
    g.setBackground(Color.WHITE);
    g.setColor(Color.BLACK);
    g.drawString(labelDense, WIDTH / 2 - 50, 2 * GRAPH_HEIGHT + EXT_PAD + 2 * PAD + 20);
    g.drawString(String.format("%s", description), SCALE_WIDTH + EXT_PAD, 30);
    AffineTransform orig = g.getTransform();
    g.rotate(-Math.toRadians(90.0), SCALE_WIDTH + EXT_PAD - 5, GRAPH_HEIGHT + EXT_PAD);
    g.drawString("Actual:", SCALE_WIDTH + EXT_PAD - 5, GRAPH_HEIGHT + EXT_PAD);
    g.setTransform(orig);
    g.rotate(-Math.toRadians(90.0), SCALE_WIDTH + EXT_PAD - 5, 2 * GRAPH_HEIGHT + EXT_PAD + PAD);
    g.drawString("Dense:", SCALE_WIDTH + EXT_PAD - 5, 2 * GRAPH_HEIGHT + EXT_PAD + PAD);
    g.setTransform(orig);
    ImageIO.write(image, "png", new File(fileName));
}

public void toImage(String fileName) throws IOException {
    if (addresses().isEmpty())
        return;
    long start = startAddress();
    long end = endAddress() + record(endAddress()).size();
    if (start == end) {
        end = start + 1;
    }
    final int WIDTH = 1000;
    final int HEIGHT = 320;
    final int GRAPH_HEIGHT = 100;
    final int SCALE_WIDTH = 30;
    final int EXT_PAD = 50;
    final int PAD = 20;
    BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = image.createGraphics();
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, WIDTH, HEIGHT);
    int minDepth = Integer.MAX_VALUE;
    int maxDepth = Integer.MIN_VALUE;
    for (long addr : addresses()) {
        GraphPathRecord p = record(addr);
        minDepth = Math.min(minDepth, p.depth());
        maxDepth = Math.max(maxDepth, p.depth());
    }
    Multiset<Integer> depths = new Multiset<Integer>();
    for (long addr : addresses()) {
        GraphPathRecord r = record(addr);
        depths.add(r.depth(), r.size());
    }
    int lastX = 0;
    for (long addr : addresses()) {
        long size = record(addr).size();
        int x1 = SCALE_WIDTH + EXT_PAD + (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * (addr - start) / (end - start));
        int x2 = SCALE_WIDTH + EXT_PAD + (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * (addr + size - start) / (end - start));
        x1 = Math.max(x1, lastX);
        x2 = Math.max(x2, lastX);
        float relDepth = 1.0f * (record(addr).depth() - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        g.fillRect(x1, EXT_PAD, x2 - x1, GRAPH_HEIGHT);
    }
    for (int depth = minDepth; depth <= maxDepth; depth++) {
        float relDepth = 1.0f * (depth - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        int y1 = HEIGHT * (depth - minDepth) / (maxDepth - minDepth + 1);
        int y2 = HEIGHT * (depth + 1 - minDepth) / (maxDepth - minDepth + 1);
        g.fillRect(0, y1, SCALE_WIDTH, y2 - y1);
    }
    lastX = SCALE_WIDTH + EXT_PAD;
    for (int depth = minDepth; depth <= maxDepth; depth++) {
        int w = (int) ((WIDTH - SCALE_WIDTH - EXT_PAD * 2) * depths.count(depth) / (end - start));
        float relDepth = 1.0f * (depth - minDepth) / (maxDepth - minDepth + 1);
        g.setColor(Color.getHSBColor(relDepth, 1.0f, 0.9f));
        g.fillRect(lastX, GRAPH_HEIGHT + EXT_PAD + PAD, w, GRAPH_HEIGHT);
        lastX += w;
    }
    g.setColor(Color.BLACK);
    g.setStroke(new BasicStroke(2.0f));
    g.drawRect(SCALE_WIDTH + EXT_PAD, EXT_PAD, WIDTH - EXT_PAD * 2 - SCALE_WIDTH, GRAPH_HEIGHT);
    g.drawRect(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT + EXT_PAD + PAD, WIDTH - EXT_PAD * 2 - SCALE_WIDTH, GRAPH_HEIGHT);
    g.setStroke(new BasicStroke(1.0f));
    g.drawLine(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD, WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD);
    g.drawLine(SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD - 5, SCALE_WIDTH + EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD + 5);
    g.drawLine(WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD - 5, WIDTH - EXT_PAD, GRAPH_HEIGHT * 2 + EXT_PAD + PAD + PAD + 5);
    Font font = new Font("Serif", Font.PLAIN, 18);
    g.setFont(font);
    String labelDense = (end - start) / 1024 + " Kb";
    g.setBackground(Color.WHITE);
    g.setColor(Color.BLACK);
    g.drawString(labelDense, WIDTH / 2 - 50, 2 * GRAPH_HEIGHT + EXT_PAD + 2 * PAD + 20);
    g.drawString(String.format("%s", description), SCALE_WIDTH + EXT_PAD, 30);
    AffineTransform orig = g.getTransform();
    g.rotate(-Math.toRadians(90.0), SCALE_WIDTH + EXT_PAD - 5, GRAPH_HEIGHT + EXT_PAD);
    g.drawString("Actual:", SCALE_WIDTH + EXT_PAD - 5, GRAPH_HEIGHT + EXT_PAD);
    g.setTransform(orig);
    g.rotate(-Math.toRadians(90.0), SCALE_WIDTH + EXT_PAD - 5, 2 * GRAPH_HEIGHT + EXT_PAD + PAD);
    g.drawString("Dense:", SCALE_WIDTH + EXT_PAD - 5, 2 * GRAPH_HEIGHT + EXT_PAD + PAD);
    g.setTransform(orig);
    ImageIO.write(image, "png", new File(fileName));
}

@Test
public void testSingleArray() throws Exception {
    int[] array = new int[10];
    File file = File.createTempFile("jol", "imagetest");
    GraphLayout.parseInstance(array).toImage(file.getAbsolutePath());
    Assert.assertTrue(file.exists());
    Assert.assertTrue(file.length() > 0);
}

@Test
public void testMultipleArrays() throws Exception {
    int[] arr1 = new int[10];
    int[] arr2 = new int[10];
    File file = File.createTempFile("jol", "imagetest");
    GraphLayout.parseInstance(arr1, arr2).toImage(file.getAbsolutePath());
    Assert.assertTrue(file.exists());
    Assert.assertTrue(file.length() > 0);
}


@Test
public void getFormat() throws IOException {
    CsvPreference first;
    CsvPreference second;
    String csv = "1\tone\n" + "2\ttwo\n" + "3\tthree";
    try (BufferedReader reader = new BufferedReader(new StringReader(csv))) {
        first = CsvUtil.getFormat(reader);
        second = CsvUtil.getFormat(reader);
    }
    assertNotSame(first.getEncoder(), second.getEncoder());
}

static public CsvPreference getFormat(BufferedReader reader) throws IOException {
    reader.mark(10 * 1024);
    for (int i = 0; i < CsvUtil.DELIMITERS.length; i++) {
        char delimiter = CsvUtil.DELIMITERS[i];
        try {
            CsvPreference format = createFormat(delimiter, '\"');
            if (checkFormat(reader, format)) {
                return format;
            }
        } finally {
            reader.reset();
        }
    }
    throw new IOException("Unrecognized CSV format");
}


@Test
public void shouldReadFileWithInvalidExtensionWhenIgnoreFileExtensionIsEnabled() {
    File sourceFile = new File("src/test/resources/employees.xlsx");
    File targetFile = new File("src/test/resources/employees_invalid.dat");
    if (!targetFile.exists()) {
        try {
            java.nio.file.Files.copy(sourceFile.toPath(), targetFile.toPath());
        } catch (Exception e) {
            throw new RuntimeException("Failed to create test file", e);
        }
    }
    PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().ignoreFileExtension(true).build();
    List<Employee> employees = Poiji.fromExcel(targetFile, Employee.class, options);
    assertThat(employees, notNullValue());
    assertThat(employees.size(), is(3));
}

public PoijiOptionsBuilder ignoreFileExtension(boolean ignoreFileExtension) {
    this.ignoreFileExtension = ignoreFileExtension;
    return this;
}

@Test
public void shouldReadFileWithNoExtensionWhenIgnoreFileExtensionIsEnabled() {
    PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().ignoreFileExtension(true).build();
    List<Employee> employees = Poiji.fromExcel(new File("src/test/resources/employees_no_extension"), Employee.class, options);
    assertThat(employees, notNullValue());
    assertThat(employees.size(), is(3));
    Employee firstEmployee = employees.get(0);
    assertThat(firstEmployee.getEmployeeId(), is(123923L));
    assertThat(firstEmployee.getName(), is("Joe"));
    assertThat(firstEmployee.getSurname(), is("Doe"));
    assertThat(firstEmployee.getAge(), is(30));
    assertThat(firstEmployee.isSingle(), is(true));
    assertThat(firstEmployee.getBirthday(), is("4/9/1987"));
}


@Test
public void shouldReadPropertiesFromFileWithNoExtensionWhenIgnoreFileExtensionIsEnabled() {
    PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().ignoreFileExtension(true).build();
    PropertyEntity properties = Poiji.fromExcelProperties(new File("src/test/resources/core_properties_no_extension"), PropertyEntity.class, options);
    assertThat(properties, notNullValue());
    assertThat(properties.getTitle(), is("TestTitle"));
    assertThat(properties.getCategory(), is("TestCategory"));
}


@Test
public void ignoreWhitespaceColumnNames() {
    List<OrgWithUnknownCellsByName> organisations = Poiji.fromExcel(new File(path), OrgWithUnknownCellsByName.class, PoijiOptions.PoijiOptionsBuilder.settings().sheetName("Organisation").caseInsensitive(true).ignoreWhitespaces(true).build());
    assertThat(organisations, notNullValue());
    assertThat(organisations.size(), is(2));
    OrgWithUnknownCellsByName firstRow = organisations.stream().filter(org -> org.getId().equals("CrEaTe")).findFirst().get();
    assertThat(firstRow.getUnknownCells().size(), is(1));
    assertThat(firstRow.getUnknownCells().get("Region"), is("EMEA"));
    OrgWithUnknownCellsByName secondRow = organisations.stream().filter(org -> org.getId().equals("8d9e6430-8626-4556-8004-079085d2df2d")).findFirst().get();
    assertThat(secondRow.getUnknownCells().size(), is(1));
    assertThat(secondRow.getUnknownCells().get("Region"), is("NA"));
}

public PoijiOptionsBuilder ignoreWhitespaces(final boolean ignoreWhitespaces) {
    this.ignoreWhitespaces = ignoreWhitespaces;
    return this;
}


@Test(expected = HeaderMissingException.class)
public void testExcelMandatoryColumn() {
    Poiji.fromExcel(new File(path), PersonByNameWithMissingColumn.class, PoijiOptions.PoijiOptionsBuilder.settings().build());
}


@Test
public void testExcelSuccessOptionalColumn() {
    Poiji.fromExcel(new File(path), PersonByNameOptionalColumn.class, PoijiOptions.PoijiOptionsBuilder.settings().build());
}


@Test
public void testMapperWithStartPageAndPageSizeZeroFalse() {
    SqlSession sqlSession = MybatisReasonableHelper.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    try {
        PageHelper.startPage(1, 0, true, true, false);
        List<User> list = userMapper.selectAll();
        PageInfo<User> page = new PageInfo<User>(list);
        assertEquals(0, list.size());
        assertEquals(0, page.getStartRow());
        assertEquals(1, page.getPageNum());
        assertEquals(183, page.getTotal());
        PageHelper.startPage(1, Integer.MAX_VALUE, true, true, false);
        list = userMapper.selectAll();
        page = new PageInfo<User>(list);
        assertEquals(183, list.size());
        assertEquals(1, page.getStartRow());
        assertEquals(1, page.getPageNum());
        assertEquals(183, page.getTotal());
    } finally {
        sqlSession.close();
    }
}

@Override
public Object intercept(Invocation invocation) throws Throwable {
    try {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        checkDialectExists();
        List resultList;
        if (!dialect.skip(ms, parameter, rowBounds)) {
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            }
            resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if (dialect != null) {
            dialect.afterAll();
        }
    }
}

@Override
public Object intercept(Invocation invocation) throws Throwable {
    try {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        checkDialectExists();
        List resultList;
        if (!dialect.skip(ms, parameter, rowBounds)) {
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            }
            resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if (dialect != null) {
            dialect.afterAll();
        }
    }
}


@Test
public void testMapperWithStartPageAndReasonableFalse() {
    SqlSession sqlSession = MybatisReasonableHelper.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    try {
        PageHelper.startPage(20, 50, true, false, false);
        List<User> list = userMapper.selectAll();
        PageInfo<User> page = new PageInfo<User>(list);
        assertEquals(0, list.size());
        assertEquals(0, page.getStartRow());
        assertEquals(20, page.getPageNum());
        assertEquals(183, page.getTotal());
        PageHelper.startPage(4, 50, true, false, false);
        list = userMapper.selectAll();
        page = new PageInfo<User>(list);
        assertEquals(33, list.size());
        assertEquals(151, page.getStartRow());
        assertEquals(4, page.getPageNum());
        assertEquals(183, page.getTotal());
        PageHelper.startPage(-1, 50, true, false, false);
        list = userMapper.selectAll();
        page = new PageInfo<User>(list);
        assertEquals(0, list.size());
        assertEquals(0, page.getStartRow());
        assertEquals(-1, page.getPageNum());
        assertEquals(183, page.getTotal());
    } finally {
        sqlSession.close();
    }
}


@Test
public void testMapperWithStartPage_OrderBy_issues_641() {
    SqlSession sqlSession = MybatisHelper.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    try {
        PageHelper.startPage(1, 0, false, false, true);
        PageHelper.orderBy("id desc");
        User user = new User();
        user.setName("刘");
        List<User> list = userMapper.selectLike(user);
        assertEquals(92, list.get(0).getId());
        assertEquals(15, list.size());
        assertEquals(15, ((Page<?>) list).getTotal());
    } finally {
        sqlSession.close();
    }
}

public static void orderBy(String orderBy) {
    Page<?> page = getLocalPage();
    if (page != null) {
        page.setOrderBy(orderBy);
    } else {
        page = new Page();
        page.setOrderBy(orderBy);
        page.setOrderByOnly(true);
        setLocalPage(page);
    }
}

public static void orderBy(String orderBy) {
    Page<?> page = getLocalPage();
    if (page != null) {
        page.setOrderBy(orderBy);
        if (page.getPageSizeZero() != null && page.getPageSizeZero() && page.getPageSize() == 0) {
            page.setOrderByOnly(true);
        }
    } else {
        page = new Page();
        page.setOrderBy(orderBy);
        page.setOrderByOnly(true);
        setLocalPage(page);
    }
}


@Test
public void testMapperWithStartPage_OrderBy() {
    SqlSession sqlSession = MybatisHelper.getSqlSession();
    CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
    try {
        PageHelper.orderBy("id desc");
        Country country = new Country();
        country.setCountryname("c");
        List<Country> list = countryMapper.selectLike(country);
        assertEquals(30, list.get(0).getId());
        assertEquals(39, list.size());
        assertEquals(39, ((Page<?>) list).getTotal());
    } finally {
        sqlSession.close();
    }
}

public static void orderBy(String orderBy) {
    Page<?> page = getLocalPage();
    if (page != null) {
        page.setOrderBy(orderBy);
    } else {
        page = new Page();
        page.setOrderBy(orderBy);
        page.setOrderByOnly(true);
        setLocalPage(page);
    }
}


@Test
public void testMapperWithStartPage_OrderBy() {
    SqlSession sqlSession = MybatisHelper.getSqlSession();
    CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
    try {
        PageHelper.startPage(1, 10, "id desc");
        List<Country> list = countryMapper.selectByOrder2("id");
        assertEquals(183, list.get(0).getId());
        assertEquals(10, list.size());
        assertEquals(183, ((Page<?>) list).getTotal());
        PageHelper.startPage(1, 10, "id desc");
        list = countryMapper.selectByOrder("countryname");
        assertEquals(183, list.get(0).getId());
        assertEquals(10, list.size());
        assertEquals(183, ((Page<?>) list).getTotal());
    } finally {
        sqlSession.close();
    }
}

public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
    Page<E> page = startPage(pageNum, pageSize);
    page.setOrderBy(orderBy);
    return page;
}


@Test
public void testMapperWithStartPage_OrderBy() {
    SqlSession sqlSession = MybatisHelper.getSqlSession();
    CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
    try {
        PageHelper.startPage(1, 10, "id desc");
        List<Country> list = countryMapper.selectChoose(183, 2);
        assertEquals(182, list.get(0).getId());
        assertEquals(10, list.size());
        assertEquals(182, ((Page<?>) list).getTotal());
        PageHelper.startPage(1, 10, "id desc");
        list = countryMapper.selectChoose(183, 2);
        assertEquals(182, list.get(0).getId());
        assertEquals(10, list.size());
        assertEquals(182, ((Page<?>) list).getTotal());
    } finally {
        sqlSession.close();
    }
}


@Test(expected = Exception.class)
public void testCountColumnInject() {
    SqlSession sqlSession = MybatisHelper.getSqlSession();
    try {
        PageHelper.startPage(1, 10).countColumn("delete from user");
    } finally {
        sqlSession.close();
    }
}

public Page<E> countColumn(String columnName) {
    this.countColumn = columnName;
    return this;
}

public Page<E> countColumn(String columnName) {
    setCountColumn(columnName);
    return this;
}


@Test
public void testKeepOrderBy() {
    CountSqlParser countSqlParser = new CountSqlParser();
    try {
        PageHelper.startPage(1, 10).keepOrderBy(true);
        Assert.assertEquals("select count(0) from ( \n" + " select * from user_info order by name desc\n" + " ) tmp_count", countSqlParser.getSmartCountSql(" select * from user_info order by name desc"));
    } finally {
        PageHelper.clearPage();
    }
    try {
        PageHelper.startPage(1, 10).keepSubSelectOrderBy(true);
        Assert.assertEquals("SELECT count(0) FROM (SELECT id, name FROM user_info ORDER BY name DESC) temp", countSqlParser.getSmartCountSql("select * from (select id, name from user_info order by name desc) temp order by id"));
    } finally {
        PageHelper.clearPage();
    }
}

public Page<E> keepOrderBy(boolean keepOrderBy) {
    this.keepOrderBy = keepOrderBy;
    return this;
}


@Test
public void valueExtraction() {
    assertEquals("foo", lazy("foo").value());
    assertEquals("foo", lazy(() -> "foo").value());
}

@Override
public A value() {
    return fn0.apply();
}

@Test
public void lazyEvaluation() {
    AtomicBoolean invoked = new AtomicBoolean(false);
    Lazy<Integer> lazy = lazy(0).flatMap(x -> {
        invoked.set(true);
        return lazy(x + 1);
    });
    assertFalse(invoked.get());
    assertEquals((Integer) 1, lazy.value());
    assertTrue(invoked.get());
}

public static <A> Lazy<A> lazy(A value) {
    return lazy(() -> value);
}

@Test
public void linearStackSafety() {
    assertEquals(STACK_EXPLODING_NUMBER, times(STACK_EXPLODING_NUMBER, f -> f.fmap(x -> x + 1), lazy(0)).value());
}


@Test
public void monoid() {
    Monoid<Integer> add = Monoid.monoid((x, y) -> x + y, 0);
    assertEquals((Integer) 3, runAll(add).apply(io(1), io(2)).unsafePerformIO());
    assertEquals((Integer) 0, runAll(add).identity().unsafePerformIO());
}

public static <A> Monoid<IO<A>> runAll(Monoid<A> monoid) {
    return RunAll.<A>runAll().apply(monoid);
}


@Test
@SuppressWarnings("unused")
public void covariantReturns() {
    Effect<String> effect = noop();
    Effect<Object> diMapL = effect.diMapL(constantly("1"));
    Effect<Object> contraMap = effect.contraMap(constantly("1"));
    Effect<Object> compose = effect.compose(constantly("1"));
    Effect<String> stringEffect = effect.discardR(constantly("1"));
    Effect<String> andThen = effect.andThen((Consumer<? super String>) noop());
}

@Override
default Effect<A> andThen(Consumer<? super A> after) {
    return a -> {
        Consumer.super.andThen(after).accept(a);
        return UNIT;
    };
}


@Test
public void invert() {
    Product2<String, String> inverted = product("a", "b").invert();
    assertEquals("b", inverted._1());
    assertEquals("a", inverted._2());
}

default Product2<_2, _1> invert() {
    return into((_1, _2) -> product(_2, _1));
}

@Test
public void staticFactoryMethod() {
    Product2<String, String> product = product("a", "b");
    assertEquals("a", product._1());
    assertEquals("b", product._2());
}

static <_1, _2> Product2<_1, _2> product(_1 _1, _2 _2) {
    return new Product2<_1, _2>() {

        @Override
        public _1 _1() {
            return _1;
        }

        @Override
        public _2 _2() {
            return _2;
        }
    };
}


@Test
public void triviality() {
    assertEquals(UNIT, trivial().identity());
    assertEquals(UNIT, trivial().apply(UNIT, UNIT));
}

public static Trivial trivial() {
    return INSTANCE;
}


@Test
@SuppressWarnings("RedundantTypeArguments")
public void inference() {
    assertEquals(just(1), Pure.<Maybe<?>>pure(Maybe::just).<Integer, Maybe<Integer>>apply(1));
}

static <F extends Functor<?, ? extends F>> Pure<F> pure(Pure<F> pure) {
    return pure;
}


@Test
public void mapsDataWithLensAndFunction() {
    Lens<List<String>, Set<Integer>, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i));
    assertEquals(singleton(1), over(lens, String::length, asList("a", "aa", "aaa")));
}

static <S, T, A, B> Lens<S, T, A, B> lens(Function<? super S, ? extends A> getter, BiFunction<? super S, ? super B, ? extends T> setter) {
    return lens(Optic.<Fn1<?, ?>, Functor<?, ?>, S, T, A, B, Functor<B, ? extends Functor<?, ?>>, Functor<T, ? extends Functor<?, ?>>, Fn1<A, Functor<B, ? extends Functor<?, ?>>>, Fn1<S, Functor<T, ? extends Functor<?, ?>>>>optic(afb -> s -> afb.apply(getter.apply(s)).fmap(b -> setter.apply(s, b))));
}


@Test
public void updatesWithLensAndNewValue() {
    Lens<List<String>, java.util.Set<Integer>, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i));
    assertEquals(singleton(5), set(lens, 5, asList("a", "aa", "aaa")));
}


@Test
public void viewsSubPartWithLens() {
    Lens<List<String>, Set<Integer>, String, Integer> lens = lens(xs -> xs.get(0), (xs, i) -> singleton(i));
    assertEquals("foo", view(lens, asList("foo", "bar", "baz")));
}


@Test
public void asStreamFocusesOnCollectionAsStream() {
    Lens.Simple<List<String>, Stream<String>> asStream = CollectionLens.asStream(ArrayList::new);
    assertEquals(asList("foo", "bar", "baz"), view(asStream, asList("foo", "bar", "baz")).collect(toList()));
    assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), asList("foo", "bar", "baz")));
}

@SuppressWarnings("RedundantTypeArguments")
public static <X, CX extends Collection<X>> Lens.Simple<CX, Stream<X>> asStream(Function<? super CX, ? extends CX> copyFn) {
    return simpleLens(Collection<X>::stream, (xsL, xsS) -> {
        CX updated = copyFn.apply(xsL);
        updated.clear();
        xsS.forEach(updated::add);
        return updated;
    });
}


@Test
public void leftFocusesOnLeftValues() {
    Lens.Simple<Either<String, Integer>, Maybe<String>> left = EitherLens.left();
    assertEquals(just("fail"), view(left, left("fail")));
    assertEquals(nothing(), view(left, right(1)));
    assertEquals(left("foo"), set(left, just("foo"), left("fail")));
    assertEquals(left("fail"), set(left, nothing(), left("fail")));
    assertEquals(left("foo"), set(left, just("foo"), right(1)));
    assertEquals(right(1), set(left, nothing(), right(1)));
}

public static <L, R> Lens.Simple<Either<L, R>, Maybe<L>> left() {
    return simpleLens(CoProduct2::projectA, (lOrR, maybeL) -> maybeL.<Either<L, R>>fmap(Either::left).orElse(lOrR));
}

@Test
public void rightFocusesOnRightValues() {
    Lens.Simple<Either<String, Integer>, Maybe<Integer>> right = EitherLens.right();
    assertEquals(just(1), view(right, right(1)));
    assertEquals(nothing(), view(right, left("fail")));
    assertEquals(right(2), set(right, just(2), right(1)));
    assertEquals(right(1), set(right, nothing(), right(1)));
    assertEquals(right(2), set(right, just(2), left("fail")));
    assertEquals(left("fail"), set(right, nothing(), left("fail")));
}

public static <L, R> Lens.Simple<Either<L, R>, Maybe<R>> right() {
    return simpleLens(CoProduct2::projectB, (lOrR, maybeR) -> maybeR.<Either<L, R>>fmap(Either::right).orElse(lOrR));
}


@Test
public void mapping() {
    Iso.Simple<Iterable<String>, Iterable<Integer>> iso = IterableLens.mapping(simpleIso(Integer::parseInt, Object::toString));
    assertThat(view(iso, emptyList()), isEmpty());
    assertThat(view(iso, singletonList("1")), iterates(1));
    assertThat(view(iso, asList("1", "2", "3")), iterates(1, 2, 3));
    assertThat(set(iso, emptyList(), emptyList()), isEmpty());
    assertThat(set(iso, singletonList(1), emptyList()), iterates("1"));
    assertThat(set(iso, singletonList(2), singletonList("1")), iterates("2"));
    assertThat(set(iso, asList(1, 2, 3), singletonList("1")), iterates("1", "2", "3"));
    assertThat(set(iso, emptyList(), singletonList("1")), isEmpty());
}

public static <A, B> Iso.Simple<Iterable<A>, Iterable<B>> mapping(Iso<A, A, B, B> abIso) {
    return simpleIso(map(view(abIso)), map(view(abIso.mirror())));
}

@Test
public void head() {
    Lens.Simple<Iterable<Integer>, Maybe<Integer>> head = IterableLens.head();
    assertEquals(just(1), view(head, asList(1, 2, 3)));
    assertEquals(nothing(), view(head, emptyList()));
    assertThat(set(head, just(1), emptyList()), iterates(1));
    assertThat(set(head, nothing(), emptyList()), isEmpty());
    assertThat(set(head, just(1), asList(2, 2, 3)), iterates(1, 2, 3));
    assertThat(set(head, nothing(), asList(2, 2, 3)), iterates(2, 3));
    assertThat(over(head, maybeX -> maybeX.fmap(x -> x + 1), emptyList()), isEmpty());
    assertThat(over(head, maybeX -> maybeX.fmap(x -> x + 1), asList(1, 2, 3)), iterates(2, 2, 3));
}

public static <A> Lens.Simple<Iterable<A>, Maybe<A>> head() {
    return simpleLens(Head::head, (s, maybeB) -> {
        Iterable<A> tail = Tail.tail(s);
        return maybeB.fmap(b -> cons(b, tail)).orElse(tail);
    });
}

@Test
public void tail() {
    Lens.Simple<Iterable<Integer>, Iterable<Integer>> tail = IterableLens.tail();
    assertThat(view(tail, asList(1, 2, 3)), iterates(2, 3));
    assertThat(view(tail, emptyList()), isEmpty());
    assertThat(set(tail, asList(2, 3), singletonList(1)), iterates(1, 2, 3));
    assertThat(set(tail, emptyList(), asList(1, 2, 3)), iterates(1));
    assertThat(set(tail, asList(1, 2, 3), emptyList()), iterates(1, 2, 3));
    assertThat(set(tail, emptyList(), emptyList()), isEmpty());
    assertThat(over(tail, map(x -> x + 1), emptyList()), isEmpty());
    assertThat(over(tail, map(x -> x + 1), asList(1, 2, 3)), iterates(1, 3, 4));
}

public static <A> Lens.Simple<Iterable<A>, Iterable<A>> tail() {
    return simpleLens(Tail::tail, fn2(Head.<A>head().andThen(o -> o.fmap(cons()).orElse(id()))).toBiFunction());
}


@Test
public void elementAtWithDefaultValueFocusesOnElementAtIndex() {
    Lens<List<String>, List<String>, String, String> at0 = ListLens.elementAt(0, "missing");
    assertEquals("foo", view(at0, asList("foo", "bar", "baz")));
    assertEquals("missing", view(at0, emptyList()));
    assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", asList("foo", "bar", "baz")));
    assertEquals(singletonList("quux"), set(at0, "quux", emptyList()));
}

public static <X> Lens.Simple<List<X>, X> elementAt(int index, X defaultValue) {
    return Lens.Simple.adapt(unLiftB(unLiftA(elementAt(index), defaultValue)));
}


@Test
public void valueAtWithDefaultValue() {
    Lens.Simple<Map<String, Integer>, Integer> atFoo = MapLens.valueAt("foo", -1);
    assertEquals((Integer) 1, view(atFoo, new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }));
    assertEquals((Integer) (-1), view(atFoo, emptyMap()));
    Map<String, Integer> updated = set(atFoo, 11, new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    });
    assertEquals(new HashMap<String, Integer>() {

        {
            put("foo", 11);
            put("bar", 2);
            put("baz", 3);
        }
    }, updated);
    assertNotSame(new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }, updated);
}

public static <K, V> Lens.Simple<Map<K, V>, V> valueAt(K k, V defaultValue) {
    return adapt(unLiftB(unLiftA(valueAt(k), defaultValue)));
}

@Test
public void valuesFocusesOnValues() {
    Lens.Simple<Map<String, Integer>, Collection<Integer>> values = MapLens.values();
    assertThat(view(values, new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }), hasItems(2, 1, 3));
    Map<String, Integer> updated = set(values, asList(1, 2), new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    });
    assertEquals(new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
        }
    }, updated);
    assertNotSame(new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }, updated);
}

public static <K, V> Lens.Simple<Map<K, V>, Collection<V>> values() {
    return simpleLens(m -> new ArrayList<>(m.values()), (m, vs) -> {
        Map<K, V> updated = new HashMap<>(m);
        Set<V> valueSet = new HashSet<>(vs);
        Set<K> matchingKeys = Filter.<Map.Entry<K, V>>filter(kv -> valueSet.contains(kv.getValue())).andThen(map(Map.Entry::getKey)).andThen(toCollection(HashSet::new)).apply(updated.entrySet());
        updated.keySet().retainAll(matchingKeys);
        return updated;
    });
}


@Test
public void unLiftTPullsTOutOfMaybe() {
    Lens<Maybe<String>, Maybe<Boolean>, Maybe<Character>, Maybe<Integer>> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123");
    assertEquals(true, set(MaybeLens.unLiftT(liftedToMaybe, false), just(3), just("321")));
}

public static <S, T, A, B> Lens<S, T, Maybe<A>, B> liftA(Lens<S, T, A, B> lens) {
    return lens.mapA(Maybe::just);
}

@Test
public void unLiftSPullsSOutOfMaybe() {
    Lens<Maybe<String>, Maybe<Boolean>, Maybe<Character>, Maybe<Integer>> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123");
    assertEquals(just('f'), view(MaybeLens.unLiftS(liftedToMaybe), "f"));
}

public static <S, T, A, B> Lens<S, T, A, B> unLiftS(Lens<Maybe<S>, T, A, B> lens) {
    return lens.mapS(Maybe::just);
}

@Test
public void liftBLiftsBToMaybe() {
    assertEquals(true, set(MaybeLens.liftB(lens, 1), nothing(), "1"));
}

public static <S, T, A, B> Lens<S, T, A, Maybe<B>> liftB(Lens<S, T, A, B> lens, B defaultB) {
    return lens.mapB(m -> m.orElse(defaultB));
}

@Test
public void liftALiftsAToMaybe() {
    assertEquals(just('1'), view(liftA(lens), "123"));
}

@Test
public void unLiftAPullsAOutOfMaybe() {
    Lens<Maybe<String>, Maybe<Boolean>, Maybe<Character>, Maybe<Integer>> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123");
    assertEquals((Character) '1', view(MaybeLens.unLiftA(liftedToMaybe, '4'), nothing()));
}

public static <S, T, A, B> Lens<S, Maybe<T>, A, B> liftT(Lens<S, T, A, B> lens) {
    return lens.mapT(Maybe::just);
}

@Test
public void unLiftBPullsBOutOfMaybe() {
    Lens<Maybe<String>, Maybe<Boolean>, Maybe<Character>, Maybe<Integer>> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123");
    assertEquals(just(true), set(MaybeLens.unLiftB(liftedToMaybe), 3, just("321")));
}

public static <S, T, A, B> Lens<S, T, A, B> unLiftB(Lens<S, T, A, Maybe<B>> lens) {
    return lens.mapB(Maybe::just);
}

@Test
public void liftTLiftsTToMaybe() {
    assertEquals(just(true), set(liftT(lens), 3, "123"));
}

@Test
public void liftSLiftsSToMaybe() {
    assertEquals((Character) '3', view(liftS(lens, "3"), nothing()));
}

public static <S, T, A, B> Lens<Maybe<S>, T, A, B> liftS(Lens<S, T, A, B> lens, S defaultS) {
    return lens.mapS(m -> m.orElse(defaultS));
}


@Test
public void stackSafe() {
    Monoid<Fn1<Integer, Identity<Integer>>> endoK = endoK(pureIdentity());
    assertEquals(new Identity<>(0), endoK.reduceLeft(replicate(STACK_EXPLODING_NUMBER, Identity::new)).apply(0));
}

public static <M extends MonadRec<?, M>, A, MA extends MonadRec<A, M>> Monoid<Fn1<A, MA>> endoK(Pure<M> pureM) {
    return EndoK.<M, A, MA>endoK().apply(pureM);
}

@Test
public void identity() {
    Monoid<Fn1<Integer, Identity<Integer>>> endoK = endoK(pureIdentity());
    assertEquals(new Identity<>(1), endoK.identity().apply(1));
}


@Test
public void predicateThatImmediatelyMatchesDoesNotChangeIO() {
    assertEquals((Integer) 0, until(constantly(true), io(0)).unsafePerformIO());
}

@Override
public A unsafePerformIO() {
    @SuppressWarnings("unchecked")
    A result = (A) trampoline(into((source, compositions) -> {
        Object res = source.unsafePerformIO();
        return compositions.isEmpty() ? terminate(res) : compositions.pop().match(zip -> recurse(tuple(io(zip.unsafePerformIO().apply(res)), compositions)), flatMap -> {
            IO<Object> next = flatMap.apply(res);
            return (next instanceof Compose<?>) ? recurse(((Compose<?>) next).deforest(compositions)) : recurse(tuple(next, compositions));
        });
    }), deforest(new LinkedList<>()));
    return result;
}

@Override
public A unsafePerformIO() {
    Lazy<Object> lazyA = LazyRec.<IO<?>, Object>lazyRec((f, io) -> {
        if (io instanceof IO.Compose<?>) {
            Compose<?> compose = (Compose<?>) io;
            Lazy<Object> head = f.apply(compose.source);
            return compose.composition.match(zip -> head.flatMap(x -> f.apply(zip).<Fn1<Object, Object>>fmap(downcast()).fmap(g -> g.apply(x))), flatMap -> head.fmap(flatMap).flatMap(f));
        }
        return lazy(io::unsafePerformIO);
    }, this);
    return downcast(lazyA.value());
}


@Test
@SuppressWarnings("unused")
public void safeDowncast() {
    CharSequence charSequence = "123";
    String s = downcast(charSequence);
    Functor<Integer, Maybe<?>> maybeInt = nothing();
    Maybe<Integer> cast = downcast(maybeInt);
}

public static <A extends B, B> A downcast(B b) {
    return Downcast.<A, B>downcast().apply(b);
}


@Test
public void andThen() {
    AtomicInteger counter = new AtomicInteger();
    Effect<AtomicInteger> inc = c -> io(sideEffect(c::incrementAndGet));
    inc.andThen(inc).apply(counter).unsafePerformIO();
    assertEquals(2, counter.get());
}

default Effect<A> andThen(Effect<A> effect) {
    return a -> apply(a).flatMap(constantly(effect.apply(a)));
}


@Test
public void comparisons() {
    assertEquals(equal(), compare(naturalOrder(), 1, 1));
    assertEquals(lessThan(), compare(naturalOrder(), 2, 1));
    assertEquals(greaterThan(), compare(naturalOrder(), 1, 2));
}

public static <A> ComparisonRelation compare(Comparator<A> aComparator, A a, A a2) {
    return compare(aComparator, a).apply(a2);
}


@Test
public void comparisons() {
    assertTrue(gteWith(naturalOrder(), 1, 2));
    assertTrue(gteWith(naturalOrder(), 1, 1));
    assertFalse(gteWith(naturalOrder(), 2, 1));
    assertTrue(gteWith(comparing(String::length), "b", "ab"));
    assertTrue(gteWith(comparing(String::length), "bc", "ab"));
}

public static <A> Boolean gteWith(Comparator<A> comparator, A y, A x) {
    return gteWith(comparator, y).apply(x);
}


@Test
public void comparisons() {
    assertTrue(gtWith(naturalOrder(), 1, 2));
    assertFalse(gtWith(naturalOrder(), 1, 1));
    assertFalse(gtWith(naturalOrder(), 2, 1));
    assertTrue(gtWith(comparing(String::length), "bb", "aaa"));
}

public static <A> Boolean gtWith(Comparator<A> comparator, A y, A x) {
    return gtWith(comparator, y).apply(x);
}


@Test
public void comparisons() {
    assertTrue(lteWith(naturalOrder(), 2, 1));
    assertTrue(lteWith(naturalOrder(), 1, 1));
    assertFalse(lteWith(naturalOrder(), 1, 2));
    assertTrue(lteWith(comparing(String::length), "ab", "b"));
    assertTrue(lteWith(comparing(String::length), "ab", "bc"));
}

public static <A> Boolean lteWith(Comparator<A> comparator, A y, A x) {
    return lteWith(comparator, y).apply(x);
}


@Test
public void comparisons() {
    assertTrue(ltWith(naturalOrder(), 2, 1));
    assertFalse(ltWith(naturalOrder(), 1, 1));
    assertFalse(ltWith(naturalOrder(), 1, 2));
    assertTrue(ltWith(comparing(String::length), "ab", "b"));
}

public static <A> Boolean ltWith(Comparator<A> comparator, A y, A x) {
    return ltWith(comparator, y).apply(x);
}


@Test
public void fromInt() {
    assertEquals(greaterThan(), ComparisonRelation.fromInt(1));
    assertEquals(greaterThan(), ComparisonRelation.fromInt(MAX_VALUE));
    assertEquals(equal(), ComparisonRelation.fromInt(0));
    assertEquals(lessThan(), ComparisonRelation.fromInt(-1));
    assertEquals(lessThan(), ComparisonRelation.fromInt(MIN_VALUE));
}

public static ComparisonRelation fromInt(int signifier) {
    return signifier > 0 ? greaterThan() : signifier == 0 ? equal() : lessThan();
}


@Test
public void semigroup() {
    assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 0));
    assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 1));
    assertEquals((Integer) 2, maxWith(naturalOrder(), 1, 2));
    assertEquals("ab", maxWith(comparing(String::length), "ab", "a"));
    assertEquals("ab", maxWith(comparing(String::length), "ab", "cd"));
    assertEquals("bc", maxWith(comparing(String::length), "a", "bc"));
}

public static <A> A maxWith(Comparator<A> compareFn, A x, A y) {
    return maxWith(compareFn, x).apply(y);
}


@Test
public void semigroup() {
    assertEquals((Integer) 1, minWith(naturalOrder(), 1, 2));
    assertEquals((Integer) 1, minWith(naturalOrder(), 1, 1));
    assertEquals((Integer) 0, minWith(naturalOrder(), 1, 0));
    assertEquals("a", minWith(comparing(String::length), "a", "ab"));
    assertEquals("ab", minWith(comparing(String::length), "ab", "cd"));
    assertEquals("c", minWith(comparing(String::length), "ab", "c"));
}

public static <A> A minWith(Comparator<A> compareFn, A x, A y) {
    return minWith(compareFn, x).apply(y);
}


@Test
public void mapSpecialization() {
    assertEquals(right(singletonMap("foo", 1)), sequence(singletonMap("foo", right(1)), Either::right));
}

public static <A, B, App extends Applicative, AppB extends Applicative<B, App>, AppMap extends Applicative<Map<A, B>, App>, MapApp extends Map<A, AppB>> AppMap sequence(MapApp mapApp, Function<Map<A, B>, ? extends AppMap> pure) {
    return Sequence.<A, B, App, AppB, AppMap, MapApp>sequence(mapApp).apply(pure);
}


@Test
public void cleanupSadPath() {
    IllegalStateException thrown = new IllegalStateException("kaboom");
    IO<Integer> hashIO = bracket(io(count), c -> io(c::incrementAndGet), c -> io(() -> {
        throw thrown;
    }));
    try {
        hashIO.unsafePerformIO();
        fail("Expected exception to be raised");
    } catch (IllegalStateException actual) {
        assertEquals(thrown, actual);
        assertEquals(1, count.get());
    }
}

public static <A, B> IO<B> bracket(IO<A> io, Fn1<? super A, ? extends IO<?>> cleanupIO, Fn1<? super A, ? extends IO<B>> bodyIO) {
    return Bracket.<A, B>bracket(io, cleanupIO).apply(bodyIO);
}

@Test
public void cleanupHappyPath() {
    IO<Integer> hashIO = bracket(io(() -> count), c -> io(c::incrementAndGet), c -> io(c::hashCode));
    assertEquals(0, count.get());
    assertEquals((Integer) count.hashCode(), hashIO.unsafePerformIO());
    assertEquals(1, count.get());
}

@Test
public void errorsInCleanupAreAddedToBodyErrors() {
    IllegalStateException bodyError = new IllegalStateException("kaboom");
    IllegalStateException cleanupError = new IllegalStateException("KABOOM");
    IO<Integer> hashIO = bracket(io(count), c -> io(() -> {
        throw cleanupError;
    }), c -> io(() -> {
        throw bodyError;
    }));
    try {
        hashIO.unsafePerformIO();
        fail("Expected exception to be raised");
    } catch (IllegalStateException actual) {
        assertEquals(bodyError, actual);
        assertArrayEquals(new Throwable[] { cleanupError }, actual.getSuppressed());
    }
}


@Test
public void hasNextIfMoreAsAndMoreBs() {
    when(as.hasNext()).thenReturn(true);
    when(bs.hasNext()).thenReturn(true);
    assertThat(combinatorialIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    return (moreAs() || currentA != null) && moreBs();
}

@Test
public void doesNotHaveNextIfMoreAsButNoBs() {
    when(as.hasNext()).thenReturn(true);
    when(bs.hasNext()).thenReturn(false);
    assertThat(combinatorialIterator.hasNext(), is(false));
}

@Test
public void doesNotHaveNextIfNoAsButMoreBs() {
    when(as.hasNext()).thenReturn(false);
    when(bs.hasNext()).thenReturn(true);
    assertThat(combinatorialIterator.hasNext(), is(false));
}

@Test
public void computesCombinationsInOrder() {
    mockIteratorToHaveValues(as, "a1", "a2");
    mockIteratorToHaveValues(bs, "b1", "b2");
    assertThat(combinatorialIterator.next(), is(tuple("a1", "b1")));
    assertThat(combinatorialIterator.next(), is(tuple("a1", "b2")));
    assertThat(combinatorialIterator.next(), is(tuple("a2", "b1")));
    assertThat(combinatorialIterator.next(), is(tuple("a2", "b2")));
}

@Override
public Tuple2<A, B> next() {
    if (currentA == null)
        currentA = asIterator.next();
    if (bsIterator.hasNext()) {
        doublyLinkedBsIterator.add(bsIterator.next());
        doublyLinkedBsIterator.previous();
    }
    Tuple2<A, B> tuple = tuple(currentA, doublyLinkedBsIterator.next());
    if (moreAs() && !moreBs()) {
        currentA = asIterator.next();
        while (doublyLinkedBsIterator.hasPrevious()) doublyLinkedBsIterator.previous();
    }
    return tuple;
}


@Test
public void doesNotHaveNextIfNoElementsLeft() {
    consingIterator.next();
    consingIterator.next();
    consingIterator.next();
    consingIterator.next();
    assertFalse(consingIterator.hasNext());
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    if (!iteratedHead) {
        iteratedHead = true;
        return head;
    }
    while (asIterator instanceof ConsingIterator && ((ConsingIterator) asIterator).iteratedHead) {
        ConsingIterator<A> cons = (ConsingIterator<A>) asIterator;
        if (cons.iteratedHead && cons.asIterator != null)
            asIterator = cons.asIterator;
    }
    return asIterator.next();
}

@Test
public void hasNextIfMoreElementsAfterHead() {
    consingIterator.next();
    assertTrue(consingIterator.hasNext());
}

@Test
public void hasNextIfHeadNotYetIterated() {
    assertTrue(consingIterator.hasNext());
}

@Override
public boolean hasNext() {
    if (!iteratedHead)
        return true;
    if (asIterator == null)
        asIterator = asSupplier.apply();
    return asIterator.hasNext();
}

@Test
public void nextIsHeadIfNotYetIterated() {
    assertEquals((Integer) 0, consingIterator.next());
}

@Test
public void stackSafety() {
    Integer stackBlowingNumber = 10_000;
    Iterable<Integer> ints = foldRight((x, lazyAcc) -> lazyAcc.fmap(acc -> () -> new ConsingIterator<>(x, acc)), lazy((Iterable<Integer>) Collections.<Integer>emptyList()), take(stackBlowingNumber, iterate(x -> x + 1, 1))).value();
    assertEquals(stackBlowingNumber, take(1, drop(stackBlowingNumber - 1, ints)).iterator().next());
}

@Override
public A next() {
    if (currentIndex >= n)
        throw new NoSuchElementException();
    currentIndex++;
    return iterator.next();
}


@Test
public void alwaysHasNext() {
    CyclicIterator<Integer> cyclicIterator = new CyclicIterator<>(asList(1, 2).iterator());
    cyclicIterator.next();
    cyclicIterator.next();
    assertThat(cyclicIterator.hasNext(), is(true));
}

@Override
public A next() {
    return iterator.hasNext() ? continueInitialIteration() : continueCachedIteration();
}

@Test
public void cyclesThroughElements() {
    CyclicIterator<String> strings = new CyclicIterator<>(asList("bait", "switch").iterator());
    strings.next();
    strings.next();
    assertThat(strings.next(), is("bait"));
}


@Test
public void dropsElementsOnNextIfNotAlreadyDropped() {
    mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5, 6);
    assertThat(droppingIterator.next(), is(6));
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    return asIterator.next();
}

@Test
public void hasNextIfIteratorHoldsMoreThanNElements() {
    mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5, 6);
    assertThat(droppingIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    if (!dropped) {
        drop();
    }
    return asIterator.hasNext();
}

@Test
public void doesNotHaveNextIfIteratorHoldsNOrLessElements() {
    mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5);
    assertThat(droppingIterator.hasNext(), is(false));
}


@Test
public void nextReturnsNextMatchingElement() {
    FilteringIterator<Character> letters = new FilteringIterator<>(x -> toUpperCase(x) == x, asList('a', 'B', 'C').iterator());
    assertThat(letters.next(), is('B'));
}

@Override
public A next() {
    if (hasNext())
        return rewindableIterator.next();
    throw new NoSuchElementException();
}

@Test
public void doesNotHaveNextIfNoRemainingElementsHoldTrueForPredicate() {
    FilteringIterator<Integer> fiveThroughTen = new FilteringIterator<>(x -> x < 5, asList(5, 6, 7, 8, 9, 10).iterator());
    assertThat(fiveThroughTen.hasNext(), is(false));
}

@Override
public boolean hasNext() {
    return rewindableIterator.isRewound() || hasMoreMatchingElements();
}

@Test
public void hasNextIfAnyElementsHoldTrueForPredicate() {
    FilteringIterator<Integer> numbersBelowFive = new FilteringIterator<>(x -> x < 5, asList(5, 3, 10, 2, 4).iterator());
    assertThat(numbersBelowFive.hasNext(), is(true));
}


@Test(timeout = 500)
public void betterLaziness() {
    assertEquals((Integer) 1, new FlatteningIterator<>(magnetize(repeat(1)).iterator()).next());
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    return xs.next();
}

@Test
public void doesNotHaveNextForEmptyIterable() {
    FlatteningIterator<Object> iterator = new FlatteningIterator<>(emptyIterator());
    assertFalse(iterator.hasNext());
}

@Override
public boolean hasNext() {
    while ((xs == null || !xs.hasNext()) && xss.hasNext()) xs = xss.next().iterator();
    return xs != null && xs.hasNext();
}

@Test
public void doesNotHaveNextForIterableOfEmptyIterables() {
    FlatteningIterator<Object> iterator = new FlatteningIterator<>(singletonList(emptyList()).iterator());
    assertFalse(iterator.hasNext());
}

@Test
public void iteratesNestedIterables() {
    FlatteningIterator<Object> iterator = new FlatteningIterator<>(asList(singletonList(1), asList(2, 3), emptyList()).iterator());
    assertEquals(1, iterator.next());
    assertEquals(2, iterator.next());
    assertEquals(3, iterator.next());
    assertFalse(iterator.hasNext());
}


@Test
public void groupsElementsInKSlices() {
    mockIteratorToHaveValues(as, 1, 2, 3, 4);
    assertThat(groupingIterator.next(), iterates(1, 2));
    assertThat(groupingIterator.next(), iterates(3, 4));
}

@Override
public Iterable<A> next() {
    List<A> group = new ArrayList<>();
    int i = 0;
    while (i++ < k && asIterator.hasNext()) group.add(asIterator.next());
    return group;
}

@Test
public void hasNextIfIteratorHasLessThanKElementsLeft() {
    mockIteratorToHaveValues(as, 1);
    assertThat(groupingIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    return asIterator.hasNext();
}

@Test
public void hasNextIfIteratorHasKMoreElements() {
    mockIteratorToHaveValues(as, 1, 2);
    assertThat(groupingIterator.hasNext(), is(true));
}

@Test
public void doesNotHaveNextIfIteratorHasNoElementsLeft() {
    assertThat(groupingIterator.hasNext(), is(false));
}


@Test(expected = UnsupportedOperationException.class)
public void doesNotSupportRemove() {
    immutableIterator.remove();
}

@Override
public final void remove() {
    throw new UnsupportedOperationException("Iterator is immutable.");
}


@Test
public void alwaysHasNext() {
    assertThat(infiniteIterator.hasNext(), is(true));
}

@Override
public final boolean hasNext() {
    return true;
}

@Test(expected = UnsupportedOperationException.class)
public void doesNotSupportRemove() {
    infiniteIterator.remove();
}


@Test
public void iteratesAllButLastElement() {
    InitIterator<Integer> iterator = new InitIterator<>(asList(1, 2, 3));
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 1, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 2, iterator.next());
    assertFalse(iterator.hasNext());
}

@Override
public boolean hasNext() {
    if (queued == null)
        if (asIterator.hasNext())
            queued = asIterator.next();
    return asIterator.hasNext();
}

@Test
public void iteratesNothingForSingleElementIterable() {
    assertFalse(new InitIterator<>(singletonList(1)).hasNext());
}

@Test
public void iteratesNothingForEmptyIterable() {
    assertFalse(new InitIterator<>(Collections::emptyIterator).hasNext());
}


@Test
public void nextProducesMappedResult() {
    Fn1<String, Integer> stringToLength = String::length;
    List<String> words = asList("foo", "bar");
    MappingIterator<String, Integer> mappingIterator = new MappingIterator<>(stringToLength, words.iterator());
    assertThat(mappingIterator.next(), is(3));
}

@Override
public B next() {
    return function.apply(iterator.next());
}


@Test
public void beginsIteratingAtFirstElementThatFailedPredicate() {
    mockIteratorToHaveValues(iterator, 0, 2, 4, 5, 6);
    assertThat(predicatedDroppingIterator.next(), is(5));
    assertThat(predicatedDroppingIterator.next(), is(6));
}

@Test(expected = NoSuchElementException.class)
public void failsIfOnNextNoElementsFailedPredicate() {
    mockIteratorToHaveValues(iterator, 0, 2, 4, 6);
    predicatedDroppingIterator.next();
}

@Test
public void hasNextIfAnyElementsFailForPredicate() {
    mockIteratorToHaveValues(iterator, 0, 2, 4, 5, 6);
    assertThat(predicatedDroppingIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    dropElementsIfNecessary();
    return rewindableIterator.hasNext();
}

@Test
public void iteratesAllElementsIfFirstElementFailedPredicate() {
    mockIteratorToHaveValues(iterator, -1, 0, 2, 4, 6);
    assertThat(predicatedDroppingIterator.next(), is(-1));
    assertThat(predicatedDroppingIterator.next(), is(0));
    assertThat(predicatedDroppingIterator.next(), is(2));
    assertThat(predicatedDroppingIterator.next(), is(4));
    assertThat(predicatedDroppingIterator.next(), is(6));
}

@Test
public void doesNotHaveNextIfNoElementsFailPredicate() {
    mockIteratorToHaveValues(iterator, 0, 2, 4, 6);
    assertThat(predicatedDroppingIterator.hasNext(), is(false));
}


@Test
public void stopsTakingAfterFirstPredicateFailure() {
    Iterable<String> words = asList("once", "upon", "a", "time");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    predicatedTakingIterator.next();
    predicatedTakingIterator.next();
    assertThat(predicatedTakingIterator.hasNext(), is(false));
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    return rewindableIterator.next();
}

@Test
public void doesNotHaveNextIfFirstElementFailsPredicate() {
    Iterable<String> words = asList("I", "have", "a", "dream");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    assertThat(predicatedTakingIterator.hasNext(), is(false));
}

@Override
public boolean hasNext() {
    return stillTaking && rewindableIterator.hasNext() && predicateSucceedsOnNextElement();
}

@Test
public void takesEverythingIfPredicateNeverFails() {
    Iterable<String> words = singletonList("yeah");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    assertThat(predicatedTakingIterator.next(), is("yeah"));
    assertThat(predicatedTakingIterator.hasNext(), is(false));
}

@Test
public void hasNextIfPredicateSucceedsForNextElement() {
    Iterable<String> words = asList("four", "three", "two", "one");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    assertThat(predicatedTakingIterator.hasNext(), is(true));
}

@Test
public void doesNotHaveNextIfTakenAllElements() {
    Iterable<String> words = asList("four", "four");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    predicatedTakingIterator.next();
    predicatedTakingIterator.next();
    assertThat(predicatedTakingIterator.hasNext(), is(false));
}

@Test(expected = NoSuchElementException.class)
public void throwsExceptionIfNextAfterFailedPredicate() {
    Iterable<String> words = singletonList("no");
    PredicatedTakingIterator<String> predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator());
    predicatedTakingIterator.next();
}


@Test
public void empty() {
    PrependingIterator<Integer> iterator = new PrependingIterator<>(0, emptyIterator());
    assertFalse(iterator.hasNext());
    thrown.expect(NoSuchElementException.class);
    iterator.next();
}

@Override
public A next() {
    if (!iterator.hasNext())
        throw new NoSuchElementException();
    return (prependNext = !prependNext) ? iterator.next() : antecedent;
}

@Test
public void nonEmpty() {
    PrependingIterator<Integer> iterator = new PrependingIterator<>(0, asList(1, 2, 3).iterator());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 0, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 1, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 0, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 2, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 0, iterator.next());
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 3, iterator.next());
    assertFalse(iterator.hasNext());
}


@Test
public void alwaysHasNext() {
    assertThat(repetitiousIterator.hasNext(), is(true));
    repetitiousIterator.next();
    assertThat(repetitiousIterator.hasNext(), is(true));
}

@Override
public A next() {
    return value;
}

@Test
public void iteratesSingleValue() {
    assertThat(repetitiousIterator.next(), is(1));
    assertThat(repetitiousIterator.next(), is(1));
    assertThat(repetitiousIterator.next(), is(1));
}


@Test
public void reversesIterator() {
    mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5);
    assertThat(reversingIterator.next(), is(5));
    assertThat(reversingIterator.next(), is(4));
    assertThat(reversingIterator.next(), is(3));
    assertThat(reversingIterator.next(), is(2));
    assertThat(reversingIterator.next(), is(1));
}

@Override
public A next() {
    if (!readyToReverse())
        prepareForReversal();
    return reversingIterator.previous();
}

@Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void doesNotReverseUntilNextIsCalled() {
    reversingIterator.hasNext();
    verify(iterator, never()).next();
}

@Override
public boolean hasNext() {
    return readyToReverse() ? reversingIterator.hasPrevious() : as.hasNext();
}

@Test
@SuppressWarnings("Duplicates")
public void doesNotHaveNextIfFinishedReversingIterator() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    reversingIterator.next();
    reversingIterator.next();
    reversingIterator.next();
    assertThat(reversingIterator.hasNext(), is(false));
}

@Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void neverInteractsWithIteratorAgainAfterInitialReverse() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    reversingIterator.next();
    verify(iterator, times(4)).hasNext();
    verify(iterator, times(3)).next();
    reversingIterator.next();
    verifyNoMoreInteractions(iterator);
}

@Test
public void doesNotHaveNextIfIteratorIsEmpty() {
    when(iterator.hasNext()).thenReturn(false);
    assertThat(reversingIterator.hasNext(), is(false));
}


@Test
public void rewindingQueuesPreviousElementUpForNext() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    rewindableIterator.next();
    rewindableIterator.next();
    rewindableIterator.rewind();
    assertThat(rewindableIterator.next(), is(2));
}

@Override
public A next() {
    if (rewound) {
        rewound = false;
        return cache.retrieve();
    }
    A next = asIterator.next();
    cache.store(next);
    return next;
}

@Test(expected = NoSuchElementException.class)
public void cannotRewindIfNoValuesIterated() {
    rewindableIterator.rewind();
}

public void rewind() {
    if (cache.isEmpty())
        throw new NoSuchElementException();
    rewound = true;
}

@Test(expected = NoSuchElementException.class)
public void cannotRewindTheSameElementTwice() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    rewindableIterator.next();
    rewindableIterator.rewind();
    rewindableIterator.next();
    rewindableIterator.rewind();
}

@Test
public void hasNextIfFullyIteratedButRewound() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    rewindableIterator.next();
    rewindableIterator.next();
    rewindableIterator.next();
    rewindableIterator.rewind();
    assertThat(rewindableIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    return rewound ? cache.isNotEmpty() : asIterator.hasNext();
}

@Test
@SuppressWarnings("Duplicates")
public void doesNotHaveNextIfNoMoreElementsAndIsNotRewound() {
    mockIteratorToHaveValues(iterator, 1, 2, 3);
    rewindableIterator.next();
    rewindableIterator.next();
    rewindableIterator.next();
    assertThat(rewindableIterator.hasNext(), is(false));
}


@Test
public void hasNextAtLeastForB() {
    ScanningIterator<Integer, Integer> scanningIterator = new ScanningIterator<>(ADD, 0, emptyIterator());
    assertThat(scanningIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    return b != null;
}

@Test
public void nextIsTrueAtLeastForB() {
    ScanningIterator<Integer, Integer> scanningIterator = new ScanningIterator<>(ADD, 0, emptyIterator());
    assertThat(scanningIterator.next(), is(0));
}

@Override
public B next() {
    if (b == null)
        throw new NoSuchElementException();
    B next = b;
    b = asIterator.hasNext() ? scanner.apply(b, asIterator.next()) : null;
    return next;
}

@Test(expected = NoSuchElementException.class)
public void failsOnNextIfOutOfAs() {
    ScanningIterator<Integer, Integer> scanningIterator = new ScanningIterator<>(ADD, 0, emptyIterator());
    scanningIterator.next();
    scanningIterator.next();
}

@Test
public void iteratesAsAfterB() {
    ScanningIterator<Integer, Integer> scanningIterator = new ScanningIterator<>(ADD, 0, asList(1, 2, 3, 4, 5).iterator());
    scanningIterator.next();
    assertThat(scanningIterator.hasNext(), is(true));
    assertThat(scanningIterator.next(), is(ADD.apply(0, 1)));
}


@Test
public void hasNextIfNoInitButLastNotYetIterated() {
    iterator.next();
    iterator.next();
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 3, iterator.next());
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    return as.hasNext() ? as.next() : snocs.next();
}

@Test
public void doesNotHaveNextIfLastIterated() {
    iterator.next();
    iterator.next();
    iterator.next();
    assertFalse(iterator.hasNext());
}

@Test
public void hasNextIfInitNotYetIterated() {
    assertTrue(iterator.hasNext());
    assertEquals((Integer) 1, iterator.next());
}

@Override
public boolean hasNext() {
    return as.hasNext() || snocs.hasNext();
}


@Test
public void hasNextBeforeTakingAnyElements() {
    List<Integer> numbers = asList(1, 2, 3, 4, 5);
    TakingIterator<Integer> takingIterator = new TakingIterator<>(3, numbers.iterator());
    assertThat(takingIterator.hasNext(), is(true));
}

@Override
public boolean hasNext() {
    return currentIndex < n && iterator.hasNext();
}

@Test
public void doesNotHaveNextForEmptyIterable() {
    TakingIterator<Object> takingIterator = new TakingIterator<>(3, Collections.emptyIterator());
    assertThat(takingIterator.hasNext(), is(false));
}

@Test
public void doesNotHaveNextIfTakenEnoughElements() {
    List<Character> vowels = asList('a', 'e', 'i', 'o', 'u');
    TakingIterator<Character> takingIterator = new TakingIterator<>(3, vowels.iterator());
    takingIterator.next();
    takingIterator.next();
    takingIterator.next();
    assertThat(takingIterator.hasNext(), is(false));
}

@Override
public A next() {
    if (currentIndex >= n)
        throw new NoSuchElementException();
    currentIndex++;
    return iterator.next();
}

@Test
public void doesNotHaveNextIfTakenAllOfIterable() {
    List<String> words = asList("we", "the", "people");
    TakingIterator<String> takingIterator = new TakingIterator<>(4, words.iterator());
    takingIterator.next();
    takingIterator.next();
    takingIterator.next();
    assertThat(takingIterator.hasNext(), is(false));
}


@Test
public void defersNextCallForAsLongAsPossible() {
    AtomicInteger invocations = new AtomicInteger(0);
    UnfoldingIterator<String, Integer> iterator = new UnfoldingIterator<>(x -> {
        invocations.incrementAndGet();
        return just(tuple(x.toString(), x + 1));
    }, 1);
    assertEquals(0, invocations.get());
    iterator.next();
    assertEquals(1, invocations.get());
}

@Override
public A next() {
    if (!hasNext())
        throw new NoSuchElementException();
    Tuple2<A, B> acc = maybeAcc.orElseThrow(NoSuchElementException::new);
    A next = acc._1();
    seed = acc._2();
    maybeAcc = null;
    return next;
}

@Test
public void doesNotHaveNextIfFunctionProducesEmptyValue() {
    assertThat(new UnfoldingIterator<String, Integer>(x -> nothing(), 0).hasNext(), is(false));
}

@Override
public boolean hasNext() {
    if (maybeAcc == null)
        maybeAcc = function.apply(seed);
    return maybeAcc.fmap(constantly(true)).orElse(false);
}

@Test
public void hasNextIfFunctionProducesPresentValue() {
    assertThat(new UnfoldingIterator<>(STRINGIFY, 0).hasNext(), is(true));
}


@Test
public void doesNotHaveNextIfAsDoesNotHaveNext() {
    when(as.hasNext()).thenReturn(false);
    when(bs.hasNext()).thenReturn(true);
    assertThat(zippingIterator.hasNext(), is(false));
}

@Override
public boolean hasNext() {
    return asIterator.hasNext() && bsIterator.hasNext();
}

@Test
public void hasNextIfMoreAsAndMoreBs() {
    when(as.hasNext()).thenReturn(true);
    when(bs.hasNext()).thenReturn(true);
    assertThat(zippingIterator.hasNext(), is(true));
}

@Test
public void zipsNextElementFromAsAndBs() {
    when(as.hasNext()).thenReturn(true);
    when(bs.hasNext()).thenReturn(true);
    when(as.next()).thenReturn(1);
    when(bs.next()).thenReturn(2);
    zippingIterator.next();
    verify(zipper).apply(1, 2);
}

@Override
public C next() {
    return zipper.apply(asIterator.next(), bsIterator.next());
}

@Test
public void doesNotHaveNextIfBsDoesNotHaveNext() {
    when(as.hasNext()).thenReturn(true);
    when(bs.hasNext()).thenReturn(false);
    assertThat(zippingIterator.hasNext(), is(false));
}


@Test
public void asCopyWithCopyFn() {
    assertLensLawfulness(MapLens.asCopy(LinkedHashMap::new), asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }), asList(emptyMap(), singletonMap("foo", 1), new HashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }));
    assertThat(view(MapLens.asCopy(LinkedHashMap::new), new LinkedHashMap<String, Integer>() {

        {
            put("foo", 1);
            put("bar", 2);
            put("baz", 3);
        }
    }).keySet(), iterates("foo", "bar", "baz"));
}

public static <M extends Map<K, V>, K, V> Lens<Map<K, V>, M, M, M> asCopy(Function<? super Map<K, V>, ? extends M> copyFn) {
    return lens(copyFn, (__, copy) -> copy);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullTrees() {
    pathToEveryLeaf.calculate(null);
}

public List<List<BinaryNode>> calculate(BinaryNode<Integer> root) {
    if (root == null) {
        throw new IllegalArgumentException("You can't pass a null instance of BinaryNode");
    }
    return calculatePathToLeafs(root, new LinkedList<BinaryNode>());
}

@Test
public void shouldReturnOneElementWithJustOneNodeIfTheTreeContainsJustOneElement() {
    BinaryNode<Integer> root = new BinaryNode<Integer>(1);
    List<List<BinaryNode>> paths = pathToEveryLeaf.calculate(root);
    assertEquals(1, paths.size());
    assertEquals(1, paths.get(0).get(0).getData());
}


@Test
public void shouldNotModifyNodeIfListContainsJustThatNode() {
    ListNode<Integer> head = new ListNode<Integer>(1);
    deleteNode.delete(head);
    assertEquals(head, head);
}

public void delete(ListNode node) {
    validateInputData(node);
    if (node.getNext() == null) {
        return;
    }
    ListNode next = node.getNext();
    node.setData(next.getData());
    node.setNext(next.getNext());
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullNodes() {
    deleteNode.delete(null);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullInstancesAsParameter() {
    removeDuplicates.remove(null);
}

public void remove(ListNode<Integer> head) {
    validateInput(head);
    Set<Integer> elements = new HashSet<Integer>();
    ListNode<Integer> currentNode = head;
    ListNode<Integer> prevNode = null;
    while (currentNode != null) {
        if (elements.contains(currentNode.getData())) {
            prevNode.setNext(currentNode.getNext());
        } else {
            elements.add(currentNode.getData());
        }
        prevNode = currentNode;
        currentNode = currentNode.getNext();
    }
}


@Test
public void shouldReturnTrueIfSecondStringContainsFirstString() {
    String word1 = "dro";
    String word2 = "Pedro";
    boolean result = contains.evaluate(word1, word2);
    assertTrue(result);
}

public boolean evaluate(String w1, String w2) {
    if (w1 == null || w2 == null) {
        throw new IllegalArgumentException("You can't pass null strings as input.");
    }
    boolean contains = false;
    for (int i = 0; i < w2.length() - 1; i++) {
        if (w2.charAt(i) == w1.charAt(0)) {
            for (int j = 0; j < w1.length(); j++) {
                if (w1.charAt(j) == w2.charAt(i + j) && j == w1.length() - 1) {
                    contains = true;
                    break;
                }
            }
        }
    }
    return contains;
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsAsInput() {
    contains.evaluate(null, null);
}

@Test
public void shouldBeCaseSensitive() {
    String word1 = "PE";
    String word2 = "Pedro";
    boolean result = contains.evaluate(word1, word2);
    assertFalse(result);
}

@Test
public void shouldReturnFalseIfSecondStringDoesNotContainFirstString() {
    String word1 = "ana";
    String word2 = "Pedro";
    boolean result = contains.evaluate(word1, word2);
    assertFalse(result);
}


@Test
public void shouldClaculateHappyCase() {
    int numA = 5;
    int numB = 1;
    int numberOfDifferentBits = bitsToTransform.calculate(numA, numB);
    assertEquals(1, numberOfDifferentBits);
}

public int calculate(int numA, int numB) {
    int differentBits = 0;
    for (int XOR = numA ^ numB; XOR != 0; XOR = XOR & (XOR - 1)) {
        differentBits++;
    }
    return differentBits;
}

@Test
public void shouldReturnTheNumberOfDifferentBitsWhenNumAIsGreater() {
    int numA = 99;
    int numB = 78;
    int numberOfDifferentBits = bitsToTransform.calculate(numA, numB);
    assertEquals(4, numberOfDifferentBits);
}

@Test
public void shouldReturnZeroIfBothNumbersAreEquals() {
    int numA = 7;
    int numB = 7;
    int numberOfDifferentBits = bitsToTransform.calculate(numA, numB);
    assertEquals(0, numberOfDifferentBits);
}

@Test
public void shouldReturnTheNumberOfDifferentBitsWhenNumAIsLower() {
    int numA = 78;
    int numB = 99;
    int numberOfDifferentBits = bitsToTransform.calculate(numA, numB);
    assertEquals(4, numberOfDifferentBits);
}


@Test
public void shouldReturnFalseIfNumberIsOdd() {
    assertFalse(isEven.check(99));
}

public boolean check(int n) {
    return (n & 1) != 1;
}

@Test
public void shouldReturnTrueIfNumberIsEven() {
    assertTrue(isEven.check(68));
}

@Test
public void shouldReturnTrueIfNumberIsZero() {
    assertTrue(isEven.check(0));
}


@Test
public void shouldReturnFalseIfS2IsNotRotationOfS1() {
    String s1 = "waterbottle";
    String s2 = "pedro";
    boolean result = isRotation.check(s1, s2);
    assertFalse(result);
}

public boolean check(String s1, String s2) {
    validateInput(s1, s2);
    if (haveSameLength(s1, s2)) {
        String s1s1 = s1 + s1;
        return isSubstring(s1s1, s2);
    }
    return false;
}

@Test
public void shouldReturnTrueIfS2IsNotRotationOfS1() {
    String s1 = "waterbottle";
    String s2 = "erbottlewat";
    boolean result = isRotation.check(s1, s2);
    assertTrue(result);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullInstancesAsSecondParameter() {
    isRotation.check("", null);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullInstancesAsFirstParameter() {
    isRotation.check(null, "");
}


@Test
public void shouldSumNumbersWithMoreThanOneDigit() {
    ListNode<Integer> n1 = createList(new int[] { 1, 5, 5 });
    ListNode<Integer> n2 = createList(new int[] { 3, 1, 4 });
    int result = sumLists.sum(n1, n2);
    assertEquals(469, result);
}

public int sum(ListNode<Integer> n1, ListNode<Integer> n2) {
    validateInput(n1, n2);
    int n1Value = calculateNumber(n1);
    int n2Value = calculateNumber(n2);
    return n1Value + n2Value;
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullListAsFirstParameter() {
    sumLists.sum(null, new ListNode<Integer>(0));
}

@Test
public void shouldSumNumbersWithDifferentSizes() {
    ListNode<Integer> n1 = createList(new int[] { 1, 5, 5 });
    ListNode<Integer> n2 = createList(new int[] { 5 });
    int result = sumLists.sum(n1, n2);
    assertEquals(160, result);
}

@Test
public void shouldSumNumbersWithJustOneDigit() {
    ListNode<Integer> n1 = createList(new int[] { 3 });
    ListNode<Integer> n2 = createList(new int[] { 8 });
    int result = sumLists.sum(n1, n2);
    assertEquals(11, result);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullListAsSecondParameter() {
    sumLists.sum(new ListNode<Integer>(0), null);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullListAsInputReversing() {
    palindromeList.checkReversing(null);
}

public boolean checkReversing(ListNode list) {
    validateInput(list);
    ListNode<Integer> reversedList = reverseLinkedList.reverseIterative(list);
    boolean isPalindrome = true;
    while (list != null) {
        isPalindrome = list.equals(reversedList);
        if (!isPalindrome) {
            break;
        }
        reversedList = reversedList.getNext();
        list = list.getNext();
    }
    return isPalindrome;
}

@Test
public void shouldReturnTrueIfListContainsJustOneNodeReversing() {
    ListNode<Integer> list = new ListNode<Integer>(1);
    boolean result = palindromeList.checkReversing(list);
    assertTrue(result);
}


@Test
public void shouldReverseStringRecursive() {
    String result = reverseString.reverseRecursive("Pedro");
    assertEquals("ordeP", result);
}

public String reverseRecursive(String input) {
    validateInput(input);
    return reverseRecursiveInner(input, input.length() - 1, new StringBuilder());
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsRecursive() {
    reverseString.reverseRecursive(null);
}

@Test
public void shouldReturnAnEmptyStringIfTheInputIsEmptyRecursive() {
    String result = reverseString.reverseRecursive("");
    assertEquals("", result);
}

@Test
public void shouldReturnTheSameStringIfTheInputStringContainsJustOneCharRecursive() {
    String result = reverseString.reverseRecursive("a");
    assertEquals("a", result);
}


@Test
public void shouldFindNthMostRepeatedElement() {
    int[] numbers = { 1, 1, 2, 3, 4, 5, 2, 2, 2, 4, 4, 6, 7, 4, 9, 214, 4, 5 };
    int result = findNthMostRepeatedElement.find(numbers, 2);
    assertEquals(1, result);
}

public int find(int[] numbers, int position) {
    validateInput(numbers, position);
    Integer result = null;
    Map<Integer, Integer> counter = new HashMap<Integer, Integer>();
    for (int i : numbers) {
        if (counter.get(i) == null) {
            counter.put(i, 1);
        } else {
            counter.put(i, counter.get(i) + 1);
        }
    }
    for (Integer candidate : counter.keySet()) {
        if (counter.get(candidate) == position) {
            result = candidate;
            break;
        }
    }
    validateResult(result);
    return result;
}

@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionIfThereAreNoElementsRepeatedNTimesInTheArrayPassedAsArgument() {
    int[] numbers = { 1, 2, 3 };
    findNthMostRepeatedElement.find(numbers, 2);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    findNthMostRepeatedElement.find(null, 9);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptInputValuesMinorThanZero() {
    findNthMostRepeatedElement.find(new int[0], 0);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArraysAsInput() {
    moveElementsToPosition.move(null);
}

public void move(int[] array) {
    if (array == null) {
        throw new IllegalArgumentException("You can't pass a null array as input.");
    }
    int size = array.length;
    boolean swap = true;
    while (swap) {
        swap = false;
        for (int i = 0; i < size - 1; i++) {
            checkIfElementIsInsideTheValidInterval(array[i], size);
            if (array[i] > array[i + 1]) {
                swap = true;
                swapElements(array, i, i + 1);
            }
        }
    }
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptAnyNumberGreaterThanArrayLengthOrMinorThanOne() {
    int[] input = { 4, 1 };
    moveElementsToPosition.move(input);
}


@Test
public void shouldReturnTrueIfInputStringIsEmpty() {
    assertTrue(palindromes.evaluate(""));
}

public boolean evaluate(String word) {
    if (word == null) {
        throw new IllegalArgumentException("You can't pass a null String as input.");
    }
    boolean result = true;
    int length = word.length();
    for (int i = 0; i < length / 2; i++) {
        if (word.charAt(i) != word.charAt(length - 1 - i)) {
            result = false;
            break;
        }
    }
    return result;
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStrings() {
    palindromes.evaluate(null);
}

@Test
public void shouldReturnFalseIfInputIsPalindrome() {
    assertFalse(palindromes.evaluate("pedro"));
}

@Test
public void shouldReturnTrueIfInputIsPalindrome() {
    assertTrue(palindromes.evaluate("ana"));
}


@Test
public void shouldReplaceSpacesWithTheStatementCharWithJustOneSpace() {
    char[] input = new char[8];
    input[0] = 'p';
    input[1] = 'e';
    input[2] = ' ';
    input[3] = 'd';
    input[4] = 'r';
    input[5] = 'o';
    replaceSpaces.replace(input);
    assertArrayEquals(new char[] { 'p', 'e', '%', '2', '0', 'd', 'r', 'o' }, input);
}

public void replace(char[] input) {
    validateInput(input);
    char[] result = new char[input.length];
    for (int i = 0, j = 0; i < input.length && j < input.length; i++, j++) {
        if (input[i] == ' ') {
            result[j++] = '%';
            result[j++] = '2';
            result[j] = '0';
        } else {
            result[j] = input[i];
        }
    }
    copyArray(result, input);
}

@Test
public void shouldReplaceSpacesWithTheStatementCharWithMoreThanOneSpace() {
    char[] input = new char[13];
    input[0] = 'p';
    input[1] = 'e';
    input[2] = ' ';
    input[3] = 'd';
    input[4] = 'r';
    input[5] = 'o';
    input[6] = ' ';
    input[7] = 'g';
    input[8] = 's';
    replaceSpaces.replace(input);
    assertArrayEquals(new char[] { 'p', 'e', '%', '2', '0', 'd', 'r', 'o', '%', '2', '0', 'g', 's' }, input);
}

@Test
public void shouldReplaceAllTheContentIfTheArrayIsFullOfSpaces() {
    char[] input = new char[6];
    input[0] = ' ';
    input[1] = ' ';
    replaceSpaces.replace(input);
    assertArrayEquals(new char[] { '%', '2', '0', '%', '2', '0' }, input);
}

@Test
public void shouldReplaceSpacesWithTheStatementCharWithJustOneSpaceAtTheEndOfTheContent() {
    char[] input = new char[8];
    input[0] = 'p';
    input[1] = 'e';
    input[2] = 'd';
    input[3] = 'r';
    input[4] = 'o';
    input[5] = ' ';
    replaceSpaces.replace(input);
    assertArrayEquals(new char[] { 'p', 'e', 'd', 'r', 'o', '%', '2', '0' }, input);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    replaceSpaces.replace(null);
}


@Test
public void shouldCompressWordsRecursive() {
    String wordToCompress = "aabcccccaaa";
    String result = compressString.compressRecursive(wordToCompress);
    assertEquals("a2bc5a3", result);
}

public String compressRecursive(String src) {
    validateInput(src);
    if (src.length() <= 1) {
        return src;
    }
    return compressRecursiveInner(src, new StringBuilder(), 1, src.charAt(0), 1);
}

@Test
public void shouldNotCompressWordsWithJustOneCharRecursive() {
    String wordToCompress = "a";
    String result = compressString.compressRecursive(wordToCompress);
    assertEquals("a", result);
}

@Test
public void shouldNotCompressEmptyWordsRecursive() {
    String wordToCompress = "";
    String result = compressString.compressRecursive(wordToCompress);
    assertEquals("", result);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsAsInputRecursive() {
    compressString.compressRecursive(null);
}

@Test
public void shouldCompressWordsFullOfTheSameCharsRecursive() {
    String wordToCompress = "aaa";
    String result = compressString.compressRecursive(wordToCompress);
    assertEquals("a3", result);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsToDeserialize() {
    binaryTreeSerialization.deserialize(null);
}

public BinaryNode<Integer> deserialize(String tree) {
    validateInput(tree);
    if (index >= tree.length()) {
        return null;
    } else {
        char currentChar = tree.charAt(index);
        if (currentChar == '#') {
            index++;
            return null;
        } else {
            BinaryNode<Integer> node = new BinaryNode<Integer>(currentChar - 48);
            index++;
            node.setLeft(deserialize(tree));
            node.setRight(deserialize(tree));
            return node;
        }
    }
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullBinaryTreesToSerialize() {
    binaryTreeSerialization.serialize(null);
}

public String serialize(BinaryNode root) {
    validateInputTree(root);
    return serializeInner(root);
}

@Test
public void shouldSerializeAndDeserializeTreeWithMoreThanOneElement() {
    BinaryNode<Integer> root = new BinaryNode<Integer>(0);
    BinaryNode<Integer> n1 = new BinaryNode<Integer>(1);
    BinaryNode<Integer> n2 = new BinaryNode<Integer>(2);
    BinaryNode<Integer> n3 = new BinaryNode<Integer>(3);
    root.setLeft(n1);
    root.setRight(n2);
    n2.setLeft(n3);
    String serializedTree = binaryTreeSerialization.serialize(root);
    BinaryNode<Integer> deserializedTree = binaryTreeSerialization.deserialize(serializedTree);
    assertEquals(root, deserializedTree);
}

@Test
public void shouldSerializeAndDeserializeTreeWithJustOneElement() {
    BinaryNode<Integer> root = new BinaryNode<Integer>(0);
    String serializedTree = binaryTreeSerialization.serialize(root);
    BinaryNode<Integer> deserializedTree = binaryTreeSerialization.deserialize(serializedTree);
    assertEquals(root, deserializedTree);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullInstancesAsInput() {
    combinationOfIntegers.calculate(null);
}

public Set<Set<Integer>> calculate(Set<Integer> originalSet) {
    validateInputSet(originalSet);
    Set<Set<Integer>> sets = new HashSet<Set<Integer>>();
    if (originalSet.isEmpty()) {
        sets.add(new HashSet<Integer>());
        return sets;
    }
    List<Integer> list = new ArrayList<Integer>(originalSet);
    Integer head = list.get(0);
    Set<Integer> rest = new HashSet<Integer>(list.subList(1, list.size()));
    for (Set<Integer> set : calculate(rest)) {
        Set<Integer> newSet = new HashSet<Integer>();
        newSet.add(head);
        newSet.addAll(set);
        sets.add(newSet);
        sets.add(set);
    }
    return sets;
}


@Test
public void shouldFindMinNumberAtPosition() {
    BinaryNode<Integer> root = new BinaryNode<Integer>(8);
    BinaryNode<Integer> n3 = new BinaryNode<Integer>(3);
    BinaryNode<Integer> n10 = new BinaryNode<Integer>(10);
    BinaryNode<Integer> n1 = new BinaryNode<Integer>(1);
    BinaryNode<Integer> n6 = new BinaryNode<Integer>(6);
    BinaryNode<Integer> n14 = new BinaryNode<Integer>(14);
    BinaryNode<Integer> n4 = new BinaryNode<Integer>(4);
    BinaryNode<Integer> n7 = new BinaryNode<Integer>(7);
    BinaryNode<Integer> n13 = new BinaryNode<Integer>(13);
    root.setLeft(n3);
    root.setRight(n10);
    n3.setLeft(n1);
    n3.setRight(n6);
    n6.setLeft(n4);
    n6.setRight(n7);
    n10.setRight(n14);
    n14.setLeft(n13);
    int result = findMinNumberAtPosition.find(root, 3);
    assertEquals(4, result);
}

public Integer find(BinaryNode<Integer> root, int position) {
    validateInputData(root, position);
    List<BinaryNode<Integer>> orderedElements = getOrderedElements(root);
    validatePosition(position, orderedElements);
    return orderedElements.get(position - 1).getData();
}

@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionIfPositionIsGreaterThanNumberOfElementsInTheTree() {
    BinaryNode<Integer> tree = new BinaryNode<Integer>(1);
    findMinNumberAtPosition.find(tree, 1);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullBinaryTrees() {
    findMinNumberAtPosition.find(null, 1);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNegativePositionsToSearch() {
    BinaryNode<Integer> tree = new BinaryNode<Integer>(1);
    findMinNumberAtPosition.find(tree, -1);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    combinationsOfChange.get(null, 7);
}

public List<List<Integer>> get(int[] availableChange, int value) {
    validateInputData(availableChange, value);
    List<List<Integer>> changeCombinations = new LinkedList<List<Integer>>();
    for (int i = 0; i < availableChange.length; i++) {
        int[] availableMoneys = Arrays.copyOfRange(availableChange, 0, availableChange.length - i);
        List<Integer> change = getChange(availableMoneys, value);
        changeCombinations.add(change);
    }
    return changeCombinations;
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptValueToChangeMinorOrEqualsToZero() {
    combinationsOfChange.get(new int[9], -3);
}

@Test
public void shouldReturnAnEmptyCollectionIfArrayOfMoneysIsEmpty() {
    List<List<Integer>> combinations = combinationsOfChange.get(new int[0], 1);
    assertTrue(combinations.isEmpty());
}


@Test
public void shouldReturnZeroIfInputsAreZero() {
    int inputA = 0;
    int inputB = 0;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(0, result);
}

public int subtract(int inputA, int inputB) {
    return inputA + negate(inputB);
}

@Test
public void shouldCalculateTheCorrectSubttractionIfBothIntegersAreNegative() {
    int inputA = -30;
    int inputB = -35;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(5, result);
}

@Test
public void shouldReturnANegativeResultIfFirstInputIsLessThanTheSecondOne() {
    int inputA = 30;
    int inputB = 35;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(-5, result);
}

@Test
public void shouldCalculateTheCorrectSubtractionIfOneOfTheIntegersIsNegative() {
    int inputA = 30;
    int inputB = -35;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(65, result);
}

@Test
public void shouldReturnZeroIfInputsAreEquals() {
    int inputA = 30;
    int inputB = 30;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(0, result);
}

@Test
public void shouldReturnAPositiveResultIfFirstInputIsGreaterThanTheSecondOne() {
    int inputA = 33;
    int inputB = 30;
    int result = subtractAdding.subtract(inputA, inputB);
    assertEquals(3, result);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringAsInput() {
    scientificNotation.calculate(null);
}

public long calculate(String number) {
    if (number == null || !number.contains("E")) {
        throw new IllegalArgumentException("Invalid format for number " + number);
    }
    String[] splittedNumber = splitScientificNumber(number);
    double first = getLeftPart(splittedNumber[0]);
    int sign = getNumberSign(splittedNumber[1]);
    int pow = getPow(splittedNumber[1]);
    Double result = sign * first * Math.pow(10, pow);
    return result.longValue();
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptMalformedScientificNumbers() {
    scientificNotation.calculate("9.18F+09");
}

@Test
public void shouldTakeIntoAccountNegativeNumbers() {
    String number = "-9.18E+09";
    long result = scientificNotation.calculate(number);
    assertEquals(-9180000000L, result);
}

@Test
public void shouldReturnTheValueOfTheNumber() {
    String number = "9.18E+09";
    long result = scientificNotation.calculate(number);
    assertEquals(9180000000L, result);
}


@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsAsInput() {
    uniqueChars.evaluate(null);
}

public boolean evaluate(String input) {
    validateInput(input);
    Map<String, Integer> chars = countCharAppearances(input);
    return containsDuplicatedChars(chars);
}

@Test
public void shouldReturnFalseIfInputStringIsEmpty() {
    assertFalse(uniqueChars.evaluate(""));
}

@Test
public void shouldReturnTruefInputStringContainsDuplicatedChars() {
    assertTrue(uniqueChars.evaluate("vicente"));
}

@Test
public void shouldReturnFalseIfInputStringDoesNotContainDuplicatedChars() {
    assertFalse(uniqueChars.evaluate("pedro"));
}


@Test
public void shouldReverseOneSentenceWithJustOneWord() {
    String input = "pedro";
    String result = reverseSentence.reverse(input);
    assertEquals("pedro", result);
}

public String reverse(String sentence) {
    if (sentence == null) {
        throw new IllegalArgumentException("Input param can't be null.");
    }
    StringBuilder stringBuilder = new StringBuilder();
    String[] words = sentence.split(WORD_SEPARATOR);
    for (int i = words.length - 1; i >= 0; i--) {
        stringBuilder.append(words[i]);
        if (i != 0) {
            stringBuilder.append(" ");
        }
    }
    return stringBuilder.toString();
}

@Test
public void shouldReturnEmptyIfInputIsEmpty() {
    assertEquals("", reverseSentence.reverse(""));
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullStringsAsInput() {
    reverseSentence.reverse(null);
}

@Test
public void shouldReverseSentenceWithMoreThanOneWord() {
    String input = "pedro vicente gómez";
    String result = reverseSentence.reverse(input);
    assertEquals("gómez vicente pedro", result);
}


@Test
public void shouldSortArray() {
    int[] input = { 3, 4, 1, 2 };
    bubbleSort.sort(input);
    int[] expectedArray = { 1, 2, 3, 4 };
    assertArrayEquals(expectedArray, input);
}

public void sort(int[] numbers) {
    validateInput(numbers);
    int length = numbers.length;
    boolean swap = true;
    while (swap) {
        swap = false;
        for (int i = 0; i < length - 1; i++) {
            for (int j = i + 1; j < length; j++) {
                if (numbers[i] > numbers[j]) {
                    swap(numbers, i, j);
                    swap = true;
                }
            }
        }
    }
}

@Test
public void shouldSortArrayWhenTheInputDataIsInDescendingOrder() {
    int[] input = { 4, 3, 2, 1 };
    bubbleSort.sort(input);
    int[] expectedArray = { 1, 2, 3, 4 };
    assertArrayEquals(expectedArray, input);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    bubbleSort.sort(null);
}

@Test
public void shouldSortArrayPartiallySorted() {
    int[] input = { 1, 2, 4, 3 };
    bubbleSort.sort(input);
    int[] expectedArray = { 1, 2, 3, 4 };
    assertArrayEquals(expectedArray, input);
}

@Test
public void shouldNotModifyArrayIfIsAlreadySorted() {
    int[] input = { 1, 2, 3, 4 };
    bubbleSort.sort(input);
    int[] expectedArray = { 1, 2, 3, 4 };
    assertArrayEquals(expectedArray, input);
}