@Test
public void testToString() {
    String duration = DurationUtils.toString(5);
    assertEquals("5s", duration);
    duration = DurationUtils.toString(60 + 1);
    assertEquals("1m1s", duration);
    duration = DurationUtils.toString(60 * 60 + 60 + 1);
    assertEquals("1h1m1s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 + 60 * 60 * 2 + 60 * 3 + 4);
    assertEquals("1d2h3m4s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5);
    assertEquals("1w2d3h4m5s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 4 * 3 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 6 + 8);
    assertEquals("3mo2d3h6m8s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5, false);
    assertEquals("5w2d3h4m5s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 4 * 3 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 6 + 8, false);
    assertEquals("12w2d3h6m8s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5, false);
    assertEquals("5w2d3h4m5s", duration);
}

@Test
public void testToString() {
    String duration = DurationUtils.toString(60 + 1);
    assertEquals("1m1s", duration);
    duration = DurationUtils.toString(60 * 60 + 60 + 1);
    assertEquals("1h1m1s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 + 60 * 60 * 2 + 60 * 3 + 4);
    assertEquals("1d2h3m4s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5);
    assertEquals("1w2d3h4m5s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 4 * 3 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 6 + 8);
    assertEquals("3mo2d3h6m8s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5, false);
    assertEquals("5w2d3h4m5s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 4 * 3 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 6 + 8, false);
    assertEquals("12w2d3h6m8s", duration);
    duration = DurationUtils.toString(60 * 60 * 8 * 5 * 5 + 60 * 60 * 8 * 2 + 60 * 60 * 3 + 60 * 4 + 5, false);
    assertEquals("5w2d3h4m5s", duration);
}

public static final String toString(int durationSeconds, boolean includeMonths) {
    int seconds = durationSeconds;
    int months = (includeMonths ? seconds / TIME_UNIT_MULTIPLIERS[0] : 0);
    seconds -= months * TIME_UNIT_MULTIPLIERS[0];
    int weeks = seconds / TIME_UNIT_MULTIPLIERS[1];
    seconds -= weeks * TIME_UNIT_MULTIPLIERS[1];
    int days = seconds / TIME_UNIT_MULTIPLIERS[2];
    seconds -= days * TIME_UNIT_MULTIPLIERS[2];
    int hours = seconds / 3600;
    seconds -= hours * 3600;
    int minutes = seconds / 60;
    seconds -= minutes * 60;
    StringBuilder buf = new StringBuilder();
    if (months > 0) {
        buf.append(months).append("mo");
        if (weeks > 0) {
            buf.append(weeks).append('w');
        }
        if (seconds > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(days).append('d').append(hours).append('h');
        } else if (days > 0) {
            buf.append(days).append('d');
        }
    } else if (weeks > 0) {
        buf.append(weeks).append('w');
        if (seconds > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(days).append('d').append(hours).append('h');
        } else if (days > 0) {
            buf.append(days).append('d');
        }
    } else if (days > 0) {
        buf.append(days).append('d');
        if (seconds > 0) {
            buf.append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(hours).append('h');
        }
    } else if (hours > 0) {
        buf.append(hours).append('h');
        if (seconds > 0) {
            buf.append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(minutes).append('m');
        }
    } else if (minutes > 0) {
        buf.append(minutes).append('m');
        if (seconds > 0) {
            buf.append(seconds).append('s');
        }
    } else {
        buf.append(seconds).append('s');
    }
    return (buf.toString());
}

public static final String toString(int durationSeconds, boolean includeMonths) {
    int seconds = durationSeconds;
    int months = (includeMonths ? seconds / TIME_UNIT_MULTIPLIERS[0] : 0);
    seconds -= months * TIME_UNIT_MULTIPLIERS[0];
    int weeks = seconds / TIME_UNIT_MULTIPLIERS[1];
    seconds -= weeks * TIME_UNIT_MULTIPLIERS[1];
    int days = seconds / TIME_UNIT_MULTIPLIERS[2];
    seconds -= days * TIME_UNIT_MULTIPLIERS[2];
    int hours = seconds / 3600;
    seconds -= hours * 3600;
    int minutes = seconds / 60;
    seconds -= minutes * 60;
    StringBuilder buf = new StringBuilder();
    if (months > 0) {
        buf.append(months).append("mo");
        if (weeks > 0) {
            buf.append(weeks).append('w');
        }
        if (seconds > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(days).append('d').append(hours).append('h');
        } else if (days > 0) {
            buf.append(days).append('d');
        }
    } else if (weeks > 0) {
        buf.append(weeks).append('w');
        if (seconds > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(days).append('d').append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(days).append('d').append(hours).append('h');
        } else if (days > 0) {
            buf.append(days).append('d');
        }
    } else if (days > 0) {
        buf.append(days).append('d');
        if (seconds > 0) {
            buf.append(hours).append('h').append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(hours).append('h').append(minutes).append('m');
        } else if (hours > 0) {
            buf.append(hours).append('h');
        }
    } else if (hours > 0) {
        buf.append(hours).append('h');
        if (seconds > 0) {
            buf.append(minutes).append('m').append(seconds).append('s');
        } else if (minutes > 0) {
            buf.append(minutes).append('m');
        }
    } else if (minutes > 0) {
        buf.append(minutes).append('m');
        if (seconds > 0) {
            buf.append(seconds).append('s');
        }
    } else {
        buf.append(' ').append(seconds).append('s');
    }
    return (buf.toString());
}


@Test
public void testGetSnippetDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

public List<Discussion> getSnippetDiscussions(Object projectIdOrPath, Integer snippetId) throws GitLabApiException {
    Pager<Discussion> pager = getSnippetDiscussionsPager(projectIdOrPath, snippetId, getDefaultPerPage());
    return (pager.all());
}

public List<Discussion> getSnippetDiscussions(Object projectIdOrPath, Long snippetId, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getSnippetDiscussions(projectIdOrPath, snippetId));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "snippets", snippetId, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

@Test
public void testGetSnippetDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1L);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}


@Test
public void trueBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, true);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("true")));
}

@Test
public void trueBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, true);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("true")));
}

public MergeRequest updateMergeRequest(Object projectIdOrPath, Integer mergeRequestIid, MergeRequestParams params) throws GitLabApiException {
    GitLabApiForm form = params.getForm(false);
    Response response = put(Response.Status.OK, form.asMap(), "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid);
    return (response.readEntity(MergeRequest.class));
}

public MergeRequest updateMergeRequest(Object projectIdOrPath, Long mergeRequestIid, String targetBranch, String title, Long assigneeId, String description, StateEvent stateEvent, String labels, Long milestoneId, Boolean removeSourceBranch, Boolean squash, Boolean discussionLocked, Boolean allowCollaboration) throws GitLabApiException {
    String[] labelsArray = null;
    if (labels != null) {
        labelsArray = labels.split(",", -1);
    }
    MergeRequestParams params = new MergeRequestParams().withTargetBranch(targetBranch).withTitle(title).withAssigneeId(assigneeId).withDescription(description).withStateEvent(stateEvent).withLabels(labelsArray).withMilestoneId(milestoneId).withRemoveSourceBranch(removeSourceBranch).withDiscussionLocked(discussionLocked).withAllowCollaboration(allowCollaboration).withSquash(squash);
    return (updateMergeRequest(projectIdOrPath, mergeRequestIid, params));
}

@Test
public void whenAllArgumentsNull_thenNoAttributesSent() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, null);
    assertEquals(0, attributeCaptor.getValue().size());
}

@Test
public void whenAllArgumentsNull_thenNoAttributesSent() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, null);
    assertEquals(0, attributeCaptor.getValue().size());
}

@Test
public void falseBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, false);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("false")));
}

@Test
public void falseBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, false);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("false")));
}


@Test
public void testGetEpicDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussions(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

@Test
public void testGetEpicDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussions(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

public List<Discussion> getEpicDiscussions(Object projectIdOrPath, Long epicId, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getEpicDiscussions(projectIdOrPath, epicId));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "epics", epicId, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

public List<Discussion> getEpicDiscussions(Object projectIdOrPath, Integer epicId, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getEpicDiscussions(projectIdOrPath, epicId));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "epics", epicId, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

@Test
public void testGetEpicDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussions(1, 1L);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

@Test
public void testGetEpicDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussions(1, 1);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

public List<Discussion> getEpicDiscussions(Object projectIdOrPath, Long epicId) throws GitLabApiException {
    Pager<Discussion> pager = getEpicDiscussionsPager(projectIdOrPath, epicId, getDefaultPerPage());
    return (pager.all());
}

public List<Discussion> getEpicDiscussions(Object projectIdOrPath, Integer epicId) throws GitLabApiException {
    Pager<Discussion> pager = getEpicDiscussionsPager(projectIdOrPath, epicId, getDefaultPerPage());
    return (pager.all());
}

@Test
public void testGetEpicDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getEpicDiscussionsStream(1, 1L);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

@Test
public void testGetEpicDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getEpicDiscussionsStream(1, 1);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "epic-discussions.json"));
}

public Stream<Discussion> getEpicDiscussionsStream(Object projectIdOrPath, Long epicId) throws GitLabApiException {
    Pager<Discussion> pager = getEpicDiscussionsPager(projectIdOrPath, epicId, getDefaultPerPage());
    return (pager.stream());
}

public Stream<Discussion> getEpicDiscussionsStream(Object projectIdOrPath, Integer epicId) throws GitLabApiException {
    Pager<Discussion> pager = getEpicDiscussionsPager(projectIdOrPath, epicId, getDefaultPerPage());
    return (pager.stream());
}

@Test
public void testGetEpicDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussionsPager(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "epic-discussions.json"));
}

@Test
public void testGetEpicDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getEpicDiscussionsPager(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "epic-discussions.json"));
}

public Pager<Discussion> getEpicDiscussionsPager(Object projectIdOrPath, Long epicId, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "epics", epicId, "discussions"));
}

public Pager<Discussion> getEpicDiscussionsPager(Object projectIdOrPath, Integer epicId, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "epics", epicId, "discussions"));
}


@Test
public void testGetIssueDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussions(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

@Test
public void testGetIssueDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussions(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

public List<Discussion> getIssueDiscussions(Object projectIdOrPath, Long issueIid, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getIssueDiscussions(projectIdOrPath, issueIid));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "issues", issueIid, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

public List<Discussion> getIssueDiscussions(Object projectIdOrPath, Integer issueIid, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getIssueDiscussions(projectIdOrPath, issueIid));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "issues", issueIid, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

@Test
public void testGetIssueDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussionsPager(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "issue-discussions.json"));
}

@Test
public void testGetIssueDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussionsPager(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "issue-discussions.json"));
}

public Pager<Discussion> getIssueDiscussionsPager(Object projectIdOrPath, Long issueIid, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "issues", issueIid, "discussions"));
}

public Pager<Discussion> getIssueDiscussionsPager(Object projectIdOrPath, Integer issueIid, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "issues", issueIid, "discussions"));
}

@Test
public void testGetIssueDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussions(1, 1L);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

@Test
public void testGetIssueDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getIssueDiscussions(1, 1);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

public List<Discussion> getIssueDiscussions(Object projectIdOrPath, Long issueIid) throws GitLabApiException {
    Pager<Discussion> pager = getIssueDiscussionsPager(projectIdOrPath, issueIid, getDefaultPerPage());
    return (pager.all());
}

public List<Discussion> getIssueDiscussions(Object projectIdOrPath, Integer issueIid) throws GitLabApiException {
    Pager<Discussion> pager = getIssueDiscussionsPager(projectIdOrPath, issueIid, getDefaultPerPage());
    return (pager.all());
}

@Test
public void testGetIssueDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getIssueDiscussionsStream(1, 1L);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

@Test
public void testGetIssueDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getIssueDiscussionsStream(1, 1);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "issue-discussions.json"));
}

public Stream<Discussion> getIssueDiscussionsStream(Object projectIdOrPath, Long issueIid) throws GitLabApiException {
    Pager<Discussion> pager = getIssueDiscussionsPager(projectIdOrPath, issueIid, getDefaultPerPage());
    return (pager.stream());
}

public Stream<Discussion> getIssueDiscussionsStream(Object projectIdOrPath, Integer issueIid) throws GitLabApiException {
    Pager<Discussion> pager = getIssueDiscussionsPager(projectIdOrPath, issueIid, getDefaultPerPage());
    return (pager.stream());
}


@Test
public void testGetMergeRequestDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussionsPager(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "merge-request-discussions.json"));
}

@Test
public void testGetMergeRequestDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussionsPager(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "merge-request-discussions.json"));
}

public Pager<Discussion> getMergeRequestDiscussionsPager(Object projectIdOrPath, Long mergeRequestIid, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid, "discussions"));
}

public Pager<Discussion> getMergeRequestDiscussionsPager(Object projectIdOrPath, Integer mergeRequestIid, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid, "discussions"));
}

@Test
public void testGetMergeRequestDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussions(1, 1L);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

@Test
public void testGetMergeRequestDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussions(1, 1);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

public List<Discussion> getMergeRequestDiscussions(Object projectIdOrPath, Long mergeRequestIid) throws GitLabApiException {
    Pager<Discussion> pager = getMergeRequestDiscussionsPager(projectIdOrPath, mergeRequestIid, getDefaultPerPage());
    return (pager.all());
}

public List<Discussion> getMergeRequestDiscussions(Object projectIdOrPath, Integer mergeRequestIid) throws GitLabApiException {
    Pager<Discussion> pager = getMergeRequestDiscussionsPager(projectIdOrPath, mergeRequestIid, getDefaultPerPage());
    return (pager.all());
}

@Test
public void testGetMergeRequestDiscussionsByListMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussions(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

@Test
public void testGetMergeRequestDiscussionsByListMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getMergeRequestDiscussions(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

public List<Discussion> getMergeRequestDiscussions(Object projectIdOrPath, Long mergeRequestIid, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getMergeRequestDiscussions(projectIdOrPath, mergeRequestIid));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

public List<Discussion> getMergeRequestDiscussions(Object projectIdOrPath, Integer mergeRequestIid, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getMergeRequestDiscussions(projectIdOrPath, mergeRequestIid));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

@Test
public void testGetMergeRequestDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getMergeRequestDiscussionsStream(1, 1L);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

@Test
public void testGetMergeRequestDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getMergeRequestDiscussionsStream(1, 1);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "merge-request-discussions.json"));
}

public Stream<Discussion> getMergeRequestDiscussionsStream(Object projectIdOrPath, Long mergeRequestIid) throws GitLabApiException {
    Pager<Discussion> pager = getMergeRequestDiscussionsPager(projectIdOrPath, mergeRequestIid, getDefaultPerPage());
    return (pager.stream());
}

public Stream<Discussion> getMergeRequestDiscussionsStream(Object projectIdOrPath, Integer mergeRequestIid) throws GitLabApiException {
    Pager<Discussion> pager = getMergeRequestDiscussionsPager(projectIdOrPath, mergeRequestIid, getDefaultPerPage());
    return (pager.stream());
}


@Test
public void testGetSnippetDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByListWithMaxItems() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

public List<Discussion> getSnippetDiscussions(Object projectIdOrPath, Long snippetId, int maxItems) throws GitLabApiException {
    if (maxItems < 1) {
        return (getSnippetDiscussions(projectIdOrPath, snippetId));
    } else {
        Response response = get(Response.Status.OK, getPerPageQueryParam(maxItems), "projects", getProjectIdOrPath(projectIdOrPath), "snippets", snippetId, "discussions");
        return (response.readEntity(new GenericType<List<Discussion>>() {
        }));
    }
}

public List<Discussion> getSnippetDiscussions(Object projectIdOrPath, Integer snippetId) throws GitLabApiException {
    Pager<Discussion> pager = getSnippetDiscussionsPager(projectIdOrPath, snippetId, getDefaultPerPage());
    return (pager.all());
}

@Test
public void testGetSnippetDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussionsPager(1, 1L, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByPager() throws Exception {
    Pager<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussionsPager(1, 1, 20);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions.all(), "snippet-discussions.json"));
}

public Pager<Discussion> getSnippetDiscussionsPager(Object projectIdOrPath, Long snippetId, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "snippets", snippetId, "discussions"));
}

public Pager<Discussion> getSnippetDiscussionsPager(Object projectIdOrPath, Integer snippetId, int itemsPerPage) throws GitLabApiException {
    return (new Pager<Discussion>(this, Discussion.class, itemsPerPage, null, "projects", getProjectIdOrPath(projectIdOrPath), "snippets", snippetId, "discussions"));
}

@Test
public void testGetSnippetDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1L);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByList() throws Exception {
    List<Discussion> discussions = new DiscussionsApi(gitLabApi).getSnippetDiscussions(1, 1);
    assertNotNull(discussions);
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getSnippetDiscussionsStream(1, 1L);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

@Test
public void testGetSnippetDiscussionsByStream() throws Exception {
    Stream<Discussion> stream = new DiscussionsApi(gitLabApi).getSnippetDiscussionsStream(1, 1);
    assertNotNull(stream);
    List<Discussion> discussions = stream.collect(Collectors.toList());
    assertTrue(compareJson(discussions, "snippet-discussions.json"));
}

public Stream<Discussion> getSnippetDiscussionsStream(Object projectIdOrPath, Long snippetId) throws GitLabApiException {
    Pager<Discussion> pager = getSnippetDiscussionsPager(projectIdOrPath, snippetId, getDefaultPerPage());
    return (pager.stream());
}

public Stream<Discussion> getSnippetDiscussionsStream(Object projectIdOrPath, Integer snippetId) throws GitLabApiException {
    Pager<Discussion> pager = getSnippetDiscussionsPager(projectIdOrPath, snippetId, getDefaultPerPage());
    return (pager.stream());
}


@Test
public void trueBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, true);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("true")));
}

@Test
public void trueBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, true);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("true")));
}

public MergeRequest updateMergeRequest(Object projectIdOrPath, Long mergeRequestIid, String targetBranch, String title, Long assigneeId, String description, StateEvent stateEvent, String labels, Long milestoneId, Boolean removeSourceBranch, Boolean squash, Boolean discussionLocked, Boolean allowCollaboration) throws GitLabApiException {
    String[] labelsArray = null;
    if (labels != null) {
        labelsArray = labels.split(",", -1);
    }
    MergeRequestParams params = new MergeRequestParams().withTargetBranch(targetBranch).withTitle(title).withAssigneeId(assigneeId).withDescription(description).withStateEvent(stateEvent).withLabels(labelsArray).withMilestoneId(milestoneId).withRemoveSourceBranch(removeSourceBranch).withDiscussionLocked(discussionLocked).withAllowCollaboration(allowCollaboration).withSquash(squash);
    return (updateMergeRequest(projectIdOrPath, mergeRequestIid, params));
}

public MergeRequest updateMergeRequest(Object projectIdOrPath, Integer mergeRequestIid, MergeRequestParams params) throws GitLabApiException {
    GitLabApiForm form = params.getForm(false);
    Response response = put(Response.Status.OK, form.asMap(), "projects", getProjectIdOrPath(projectIdOrPath), "merge_requests", mergeRequestIid);
    return (response.readEntity(MergeRequest.class));
}

@Test
public void whenAllArgumentsNull_thenNoAttributesSent() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, null);
    assertEquals(0, attributeCaptor.getValue().size());
}

@Test
public void whenAllArgumentsNull_thenNoAttributesSent() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, null);
    assertEquals(0, attributeCaptor.getValue().size());
}

@Test
public void falseBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2L, null, null, null, null, null, null, null, null, null, null, false);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("false")));
}

@Test
public void falseBooleansAreSerializedCorrectly() throws Exception {
    new MergeRequestApi(mockGitLabApi).updateMergeRequest(1, 2, null, null, null, null, null, null, null, null, null, null, false);
    assertThat(attributeCaptor.getValue(), hasEntry("allow_collaboration", Collections.singletonList("false")));
}


@Test
public void contentsAsString_fail() {
    expectFailure.whenTesting().about(javaFileObjects()).that(CLASS).contentsAsString(UTF_8).containsMatch("bad+");
    AssertionError expected = expectFailure.getFailure();
    assertThat(expected.getMessage()).containsMatch("the contents of .*" + Pattern.quote(CLASS.getName()));
    assertThat(expected.getMessage()).contains("bad+");
}

@Test
public void contentsAsString_fail() {
    expectFailure.whenTesting().about(javaFileObjects()).that(CLASS).contentsAsString(UTF_8).containsMatch("bad+");
    AssertionError expected = expectFailure.getFailure();
    assertThat(expected).factValue("value of").isEqualTo("javaFileObject.contents()");
    assertThat(expected).factValue("javaFileObject was").startsWith(CLASS.getName());
    assertThat(expected).factValue("expected to contain a match for").isEqualTo("bad+");
}

public StringSubject contentsAsString(Charset charset) {
    try {
        return check().withMessage("the contents of " + actualAsString()).that(JavaFileObjects.asByteSource(actual()).asCharSource(charset).read());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public StringSubject contentsAsString(Charset charset) {
    try {
        return check("contents()").that(JavaFileObjects.asByteSource(actual()).asCharSource(charset).read());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}


@Test
public void failsToCompile_throwsNoMessage() {
    expectFailure.whenTesting().about(javaSource()).that(JavaFileObjects.forResource("HelloWorld.java")).processedWith(new ErrorProcessor()).failsToCompile().withErrorContaining("some error");
    AssertionError expected = expectFailure.getFailure();
    assertThat(expected.getMessage()).startsWith("Expected an error containing \"some error\", but only found:\n");
    assertThat(expected.getMessage()).endsWith("expected error!\n");
}

@Test
public void failsToCompile_throwsNoMessage() {
    expectFailure.whenTesting().about(javaSource()).that(JavaFileObjects.forResource("HelloWorld.java")).processedWith(new ErrorProcessor()).failsToCompile().withErrorContaining("some error");
    AssertionError expected = expectFailure.getFailure();
    assertThat(expected.getMessage()).contains("Expected an error containing \"some error\", but only found:\n");
    assertThat(expected).hasMessageThat().contains("expected error!\n");
}

@CanIgnoreReturnValue
@Override
public UnsuccessfulCompilationClause failsToCompile() {
    Compilation compilation = compilation();
    check().about(compilations()).that(compilation).failed();
    return new UnsuccessfulCompilationBuilder(compilation);
}

@CanIgnoreReturnValue
@Override
public UnsuccessfulCompilationClause failsToCompile() {
    Compilation compilation = compilation();
    check("compilation()").about(compilations()).that(compilation).failed();
    return new UnsuccessfulCompilationBuilder(compilation);
}


@Test
public void test_method_with_add_two_int() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/AddTwoInt.class"));
    Method main = cf.methods.methods[2];
    com.gxk.jvm.classfile.attribute.Code attribute = (com.gxk.jvm.classfile.attribute.Code) main.attributes.attributes[0];
    MethodInfo method = map(attribute);
    Env env = new Env(cf.cpInfo);
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method.code.maxLocals, method.code.maxStacks, thread, env);
    thread.pushFrame(frame);
    frame.localVars.setInt(0, 10);
    frame.localVars.setInt(1, 20);
    new Interpreter().loop(thread, method.code.code);
}

@Test
public void test_method_with_add_two_int() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/AddTwoInt.class"));
    Method main = cf.methods.methods[2];
    com.gxk.jvm.classfile.attribute.Code attribute = (com.gxk.jvm.classfile.attribute.Code) main.attributes.attributes[0];
    MethodInfo method = map(attribute);
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method.code.maxLocals, method.code.maxStacks, method.code.code, thread);
    thread.pushFrame(frame);
    frame.localVars.setInt(0, 10);
    frame.localVars.setInt(1, 20);
    new Interpreter().loop(thread);
}

public void loop(Thread thread, CodeFromByte code) {
    Frame frame = thread.currentFrame();
    while (true) {
        int pc = frame.nextPc;
        thread.setPc(pc);
        Instruction inst = code.getInst(pc);
        frame.nextPc += inst.offset();
        inst.fetchOperands();
        inst.execute(frame);
        if (inst instanceof IreturnInst || inst instanceof ReturnInst) {
            break;
        }
    }
}

public void loop(Thread thread) {
    while (true) {
        Frame frame = thread.currentFrame();
        int pc = frame.nextPc;
        thread.setPc(pc);
        Instruction inst = frame.code.getInst(pc);
        frame.nextPc += inst.offset();
        inst.fetchOperands();
        inst.execute(frame);
        if (thread.empty()) {
            break;
        }
    }
}

@Test
public void test_method_with_args() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/Loop3.class"));
    Method main = cf.methods.methods[2];
    com.gxk.jvm.classfile.attribute.Code attribute = (com.gxk.jvm.classfile.attribute.Code) main.attributes.attributes[0];
    MethodInfo method = map(attribute);
    Env env = new Env(cf.cpInfo);
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method.code.maxLocals, method.code.maxStacks, thread, env);
    thread.pushFrame(frame);
    frame.localVars.setInt(0, 1000);
    new Interpreter().loop(thread, method.code.code);
}

@Test
public void test_method_with_args() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/Loop3.class"));
    Method main = cf.methods.methods[2];
    com.gxk.jvm.classfile.attribute.Code attribute = (com.gxk.jvm.classfile.attribute.Code) main.attributes.attributes[0];
    MethodInfo method = map(attribute);
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method.code.maxLocals, method.code.maxStacks, method.code.code, thread);
    thread.pushFrame(frame);
    frame.localVars.setInt(0, 1000);
    new Interpreter().loop(thread);
}


@Test
public void test_method_invoke() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/Loop4.class"));
    KClass main = Classloader.doLoadClass("Loop4", cf);
    KMethod method = main.getMainMethod();
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method, thread);
    thread.pushFrame(frame);
    new Interpreter().loop(thread);
}

@Test
public void test_method_invoke() throws Exception {
    ClassFile cf = ClassReader.read(Paths.get("example/Loop4.class"));
    KClass main = Classloader.doLoadClass("Loop4", cf);
    Classloader.doRegister(main);
    KMethod method = main.getMainMethod();
    Thread thread = new Thread(1024);
    Frame frame = new Frame(method, thread);
    thread.pushFrame(frame);
    new Interpreter().loop(thread);
}

public static void doRegister(KClass clazz) {
    Heap.registerClass(clazz.name, clazz);
    for (KMethod method : clazz.getMethods()) {
        Heap.registerMethod(method.descriptor, method);
    }
}


@Test
public void shouldSendArgumentsToMinion() {
    this.testee.apply(this.os);
    verify(this.os).write(this.arguments);
}

@Test
public void shouldSendArgumentsToMinion() {
    this.testee.accept(this.os);
    verify(this.os).write(this.arguments);
}

@Override
public void accept(final SafeDataOutputStream dos) {
    sendArguments(dos);
    sendTests(dos);
}

@Test
public void shouldSendTestClassesToMinion() {
    this.testClasses.add("foo");
    this.testClasses.add("bar");
    this.testee.apply(this.os);
    verify(this.os).writeInt(this.testClasses.size());
    verify(this.os).writeString("foo");
    verify(this.os).writeString("bar");
}

@Test
public void shouldSendTestClassesToMinion() {
    this.testClasses.add("foo");
    this.testClasses.add("bar");
    this.testee.accept(this.os);
    verify(this.os).writeInt(this.testClasses.size());
    verify(this.os).writeString("foo");
    verify(this.os).writeString("bar");
}


@Test
public void shouldDescribeActiveFeatures() {
    final SideEffect1<Feature> disabled = Mockito.mock(SideEffect1.class);
    final SideEffect1<Feature> enabled = Mockito.mock(SideEffect1.class);
    this.options.setFeatures(Arrays.asList("+FSTATINIT"));
    this.testee.describeFeatures(enabled, disabled);
    verify(enabled).apply(Feature.named("FSTATINIT"));
    verify(disabled, never()).apply(Feature.named("FSTATINIT"));
}

@Test
public void shouldDescribeActiveFeatures() {
    final Consumer<Feature> disabled = Mockito.mock(Consumer.class);
    final Consumer<Feature> enabled = Mockito.mock(Consumer.class);
    this.options.setFeatures(Arrays.asList("+FSTATINIT"));
    this.testee.describeFeatures(enabled, disabled);
    verify(enabled).accept(Feature.named("FSTATINIT"));
    verify(disabled, never()).accept(Feature.named("FSTATINIT"));
}

public void describeFeatures(SideEffect1<Feature> enabled, SideEffect1<Feature> disabled) {
    final FeatureParser parser = new FeatureParser();
    final Collection<ProvidesFeature> available = new ArrayList<>(this.plugins.findInterceptors());
    final List<FeatureSetting> settings = parser.parseFeatures(this.options.getFeatures());
    final FeatureSelector<ProvidesFeature> selector = new FeatureSelector<>(settings, available);
    List<Feature> enabledFeatures = selector.getActiveFeatures().stream().map(toFeature()).distinct().sorted(byName()).collect(Collectors.toList());
    enabledFeatures.stream().forEach(each -> enabled.apply(each));
    available.stream().map(toFeature()).distinct().sorted(byName()).filter(f -> !enabledFeatures.contains(f)).forEach(each -> disabled.apply(each));
}

public void describeFeatures(Consumer<Feature> enabled, Consumer<Feature> disabled) {
    final FeatureParser parser = new FeatureParser();
    final Collection<ProvidesFeature> available = new ArrayList<>(this.plugins.findInterceptors());
    final List<FeatureSetting> settings = parser.parseFeatures(this.options.getFeatures());
    final FeatureSelector<ProvidesFeature> selector = new FeatureSelector<>(settings, available);
    List<Feature> enabledFeatures = selector.getActiveFeatures().stream().map(toFeature()).distinct().sorted(byName()).collect(Collectors.toList());
    enabledFeatures.forEach(enabled);
    available.stream().map(toFeature()).distinct().sorted(byName()).filter(f -> !enabledFeatures.contains(f)).forEach(disabled);
}

@Test
public void shouldDescribeDisabledFeatures() {
    final SideEffect1<Feature> disabled = Mockito.mock(SideEffect1.class);
    final SideEffect1<Feature> enabled = Mockito.mock(SideEffect1.class);
    this.options.setFeatures(Arrays.asList("-FSTATINIT"));
    this.testee.describeFeatures(enabled, disabled);
    verify(enabled, never()).apply(Feature.named("FSTATINIT"));
    verify(disabled).apply(Feature.named("FSTATINIT"));
}

@Test
public void shouldDescribeDisabledFeatures() {
    final Consumer<Feature> disabled = Mockito.mock(Consumer.class);
    final Consumer<Feature> enabled = Mockito.mock(Consumer.class);
    this.options.setFeatures(Arrays.asList("-FSTATINIT"));
    this.testee.describeFeatures(enabled, disabled);
    verify(enabled, never()).accept(Feature.named("FSTATINIT"));
    verify(disabled).accept(Feature.named("FSTATINIT"));
}


@Test
public void shouldReturnUniqueTestsForClassWhenSomeTestsCoverClass() {
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest", 0, 1));
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest", 0, 2));
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest2", 0, 2));
    assertEquals(Arrays.asList("fooTest", "fooTest2"), FCollection.map(this.testee.getTestsForClass(this.foo), testInfoToString()));
}

@Test
public void shouldReturnUniqueTestsForClassWhenSomeTestsCoverClass() {
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest", 0, 1));
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest", 0, 2));
    this.testee.calculateClassCoverage(makeCoverageResult("foo", "fooTest2", 0, 2));
    List<String> actual = FCollection.map(this.testee.getTestsForClass(this.foo), testInfoToString());
    assertThat(actual).containsExactlyInAnyOrder("fooTest", "fooTest2");
}

@Override
public Collection<TestInfo> getTestsForClass(final ClassName clazz) {
    return this.instructionCoverage.entrySet().stream().filter(isFor(clazz)).flatMap(toTests()).collect(toCollection(() -> new TreeSet<>(new TestInfoNameComparator())));
}

@Override
public Collection<TestInfo> getTestsForClass(final ClassName clazz) {
    return classCoverage.getOrDefault(clazz, Collections.emptySet());
}


@Test
public void defaultConfigTest() {
    final IDataMap dataMap = new MyFooDataMapExtra();
    final Segment segment = new DataMapFastForwardSegment(dataMap);
    ZhConvertBootstrap bs = ZhConvertBootstrap.newInstance().dataMap(dataMap).segment(segment);
    Assert.assertEquals(bs.toTraditional("人生自是有情痴,此恨不关风月"), "人生自是有情癡,此恨不關風玥");
    Assert.assertEquals(bs.toSimple("人生自是有情癡,此恨不關風玥"), "人生自是有情痴,此恨不关风月");
}

@Test
public void defaultConfigTest() {
    final IDataMap dataMap = new MyFooDataMapExtra();
    final Segment segment = new DataMapFastForwardSegment(dataMap);
    ZhConvertBootstrap bs = ZhConvertBootstrap.newInstance().dataMap(dataMap).segment(segment).init();
    Assert.assertEquals(bs.toTraditional("人生自是有情痴,此恨不关风月"), "人生自是有情癡,此恨不關風玥");
    Assert.assertEquals(bs.toSimple("人生自是有情癡,此恨不關風玥"), "人生自是有情痴,此恨不关风月");
}

public ZhConvertBootstrap init() {
    this.context = ZhConvertCoreContext.newInstance().zhChars(zhChar).segment(segment).unitConvert(unitConvert).dataMap(dataMap);
    return this;
}


@Test
public void defineSegmentTest() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final Segment segment = new FooSegment();
    final String result = ZhConvertBootstrap.newInstance().segment(segment).toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result);
}

@Test
public void defineSegmentTest() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final Segment segment = new FooSegment();
    final String result = ZhConvertBootstrap.newInstance().segment(segment).init().toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result);
}


@Test
public void isTraditional() {
    final String simpleChar = "奋";
    final String simplePhrase = "奋斗";
    final String traditionalChar = "奮";
    final String traditionalPhrase = "奮鬥";
    ZhConvertBootstrap convertBootstrap = ZhConvertBootstrap.newInstance();
    Assert.assertTrue(convertBootstrap.isTraditional(traditionalChar));
    Assert.assertTrue(convertBootstrap.isTraditional(traditionalPhrase));
    Assert.assertFalse(convertBootstrap.isTraditional(simpleChar));
    Assert.assertFalse(convertBootstrap.isTraditional(simplePhrase));
}

@Test
public void isTraditional() {
    final String simpleChar = "奋";
    final String simplePhrase = "奋斗";
    final String traditionalChar = "奮";
    final String traditionalPhrase = "奮鬥";
    ZhConvertBootstrap convertBootstrap = ZhConvertBootstrap.newInstance().init();
    Assert.assertTrue(convertBootstrap.isTraditional(traditionalChar));
    Assert.assertTrue(convertBootstrap.isTraditional(traditionalPhrase));
    Assert.assertFalse(convertBootstrap.isTraditional(simpleChar));
    Assert.assertFalse(convertBootstrap.isTraditional(simplePhrase));
}

@Override
public boolean isTraditional(String charOrPhrase) {
    if (StringUtil.isEmpty(charOrPhrase)) {
        return false;
    }
    List<String> chars = toCharList(charOrPhrase);
    for (String c : chars) {
        if (!isTraditionalForSingle(c)) {
            return false;
        }
    }
    return true;
}

@Override
public boolean isTraditional(String charOrPhrase) {
    return zhConvertCore.isTraditional(charOrPhrase, context);
}

@Test
public void traditionalListTest() {
    final String original = "生命不息奮鬥不止";
    ZhConvertBootstrap zhConvertBootstrap = ZhConvertBootstrap.newInstance();
    final List<String> resultList = zhConvertBootstrap.traditionalList(original);
    String expectToString = "[生, 命, 不, 息, 奮, 鬥, 不, 止]";
    Assert.assertEquals(expectToString, resultList.toString());
}

@Test
public void traditionalListTest() {
    final String original = "生命不息奮鬥不止";
    ZhConvertBootstrap zhConvertBootstrap = ZhConvertBootstrap.newInstance().init();
    final List<String> resultList = zhConvertBootstrap.traditionalList(original);
    String expectToString = "[生, 命, 不, 息, 奮, 鬥, 不, 止]";
    Assert.assertEquals(expectToString, resultList.toString());
}

@Override
public List<String> traditionalList(String original) {
    List<String> traditionalList = Guavas.newArrayList();
    List<String> segList = this.doSeg(original);
    for (String seg : segList) {
        if (isTraditional(seg)) {
            traditionalList.add(seg);
        }
    }
    return traditionalList;
}

@Override
public List<String> traditionalList(String original) {
    return zhConvertCore.traditionalList(original, context);
}

@Test
public void segmentTest() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final String result = ZhConvertBootstrap.newInstance().toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。", result);
    final String result2 = ZhConvertBootstrap.newInstance().segment(new FooSegment()).toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result2);
}

@Test
public void segmentTest() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final String result = ZhConvertBootstrap.newInstance().init().toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。", result);
    final String result2 = ZhConvertBootstrap.newInstance().segment(new FooSegment()).init().toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result2);
}

@Test
public void toSimpleTest() {
    final String original = "生命不息，奮鬥不止";
    final String result = ZhConvertBootstrap.newInstance().toSimple(original);
    Assert.assertEquals("生命不息，奋斗不止", result);
}

@Test
public void toSimpleTest() {
    final String original = "生命不息，奮鬥不止";
    final String result = ZhConvertBootstrap.newInstance().init().toSimple(original);
    Assert.assertEquals("生命不息，奋斗不止", result);
}

@Override
public String toSimple(String original) {
    return this.convert(original, segment, dataMap.tsPhrase(), dataMap.tsChar());
}

@Override
public String toSimple(String original) {
    return zhConvertCore.toSimple(original, context);
}

@Test
public void toTraditionalTest() {
    final String original = "生命不息，奋斗不止";
    final String result = ZhConvertBootstrap.newInstance().toTraditional(original);
    Assert.assertEquals("生命不息，奮鬥不止", result);
}

@Test
public void toTraditionalTest() {
    final String original = "生命不息，奋斗不止";
    final String result = ZhConvertBootstrap.newInstance().init().toTraditional(original);
    Assert.assertEquals("生命不息，奮鬥不止", result);
}

@Override
public String toTraditional(String original) {
    return this.convert(original, segment, dataMap.stPhrase(), dataMap.stChar());
}

@Override
public String toTraditional(String original) {
    return zhConvertCore.toTraditional(original, context);
}

@Test
public void isSimpleTest() {
    final String simpleChar = "奋";
    final String simplePhrase = "奋斗";
    final String traditionalChar = "奮";
    final String traditionalPhrase = "奮鬥";
    ZhConvertBootstrap convertBootstrap = ZhConvertBootstrap.newInstance();
    Assert.assertTrue(convertBootstrap.isSimple(simpleChar));
    Assert.assertTrue(convertBootstrap.isSimple(simplePhrase));
    Assert.assertFalse(convertBootstrap.isSimple(traditionalChar));
    Assert.assertFalse(convertBootstrap.isSimple(traditionalPhrase));
}

@Test
public void isSimpleTest() {
    final String simpleChar = "奋";
    final String simplePhrase = "奋斗";
    final String traditionalChar = "奮";
    final String traditionalPhrase = "奮鬥";
    ZhConvertBootstrap convertBootstrap = ZhConvertBootstrap.newInstance().init();
    Assert.assertTrue(convertBootstrap.isSimple(simpleChar));
    Assert.assertTrue(convertBootstrap.isSimple(simplePhrase));
    Assert.assertFalse(convertBootstrap.isSimple(traditionalChar));
    Assert.assertFalse(convertBootstrap.isSimple(traditionalPhrase));
}

@Override
public boolean isSimple(String charOrPhrase) {
    if (StringUtil.isEmpty(charOrPhrase)) {
        return false;
    }
    List<String> chars = toCharList(charOrPhrase);
    for (String c : chars) {
        if (!isSimpleForSingle(c)) {
            return false;
        }
    }
    return true;
}

@Override
public boolean isSimple(String charOrPhrase) {
    return zhConvertCore.isSimple(charOrPhrase, context);
}

@Test
public void segment2Test() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final String result = ZhConvertBootstrap.newInstance(new FooSegment()).toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result);
}

@Test
public void segment2Test() {
    final String original = "寥落古行宫，宫花寂寞红。白头宫女在，闲坐说玄宗。";
    final String result = ZhConvertBootstrap.newInstance(new FooSegment()).init().toTraditional(original);
    Assert.assertEquals("寥落古行宮，宮花寂寞紅。白頭宮女在，閒坐說玄宗。測試", result);
}


@Test
void generatedMapShouldNotBeEmpty() {
    assertThat(aNewMapRandomizer(keyRandomizer, valueRandomizer).getRandomValue()).isNotEmpty();
}

@Test
void generatedMapShouldNotBeEmpty() {
    assertThat(new MapRandomizer(keyRandomizer, valueRandomizer).getRandomValue()).isNotEmpty();
}

@Deprecated
public static <K, V> MapRandomizer<K, V> aNewMapRandomizer(final Randomizer<K> keyRandomizer, final Randomizer<V> valueRandomizer) {
    return new MapRandomizer<>(keyRandomizer, valueRandomizer, getRandomSize());
}

@Test
void specifiedSizeCanBeZero() {
    assertThat(aNewMapRandomizer(keyRandomizer, valueRandomizer, 0).getRandomValue()).isEmpty();
}

@Test
void specifiedSizeCanBeZero() {
    assertThat(new MapRandomizer(keyRandomizer, valueRandomizer, 0).getRandomValue()).isEmpty();
}

@Deprecated
public static <K, V> MapRandomizer<K, V> aNewMapRandomizer(final Randomizer<K> keyRandomizer, final Randomizer<V> valueRandomizer, final int nbEntries) {
    return new MapRandomizer<>(keyRandomizer, valueRandomizer, nbEntries);
}

@Test
void generatedMapSizeShouldBeEqualToTheSpecifiedSize() {
    when(keyRandomizer.getRandomValue()).thenReturn(1, 2, 3);
    assertThat(aNewMapRandomizer(keyRandomizer, valueRandomizer, 3).getRandomValue()).hasSize(3);
}

@Test
void generatedMapSizeShouldBeEqualToTheSpecifiedSize() {
    when(keyRandomizer.getRandomValue()).thenReturn(1, 2, 3);
    assertThat(new MapRandomizer(keyRandomizer, valueRandomizer, 3).getRandomValue()).hasSize(3);
}


@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    assertThat(aNewBooleanRandomizer(SEED).getRandomValue()).isTrue();
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    assertThat(new BooleanRandomizer(SEED).getRandomValue()).isTrue();
}

@Deprecated
public static BooleanRandomizer aNewBooleanRandomizer(final long seed) {
    return new BooleanRandomizer(seed);
}

@Test
void generatedBooleanShouldNotBeNull() {
    assertThat(aNewBooleanRandomizer().getRandomValue()).isNotNull();
}

@Test
void generatedBooleanShouldNotBeNull() {
    assertThat(new BooleanRandomizer().getRandomValue()).isNotNull();
}

@Deprecated
public static BooleanRandomizer aNewBooleanRandomizer() {
    return new BooleanRandomizer();
}


@Test
void shouldAlwaysGenerateTheSameValue() {
    assertThat(aNewConstantRandomizer("a").getRandomValue()).isEqualTo("a");
}

@Test
void shouldAlwaysGenerateTheSameValue() {
    assertThat(new ConstantRandomizer("a").getRandomValue()).isEqualTo("a");
}

@Deprecated
public static <T> ConstantRandomizer<T> aNewConstantRandomizer(final T value) {
    return new ConstantRandomizer<>(value);
}


@Test
void should_return_a_value_different_from_the_excluded_one() {
    Gender valueToExclude = Gender.MALE;
    Gender randomElement = aNewEnumRandomizer(Gender.class, valueToExclude).getRandomValue();
    assertThat(randomElement).isNotNull();
    assertThat(randomElement).isNotEqualTo(valueToExclude);
}

@Test
void should_return_a_value_different_from_the_excluded_one() {
    Gender valueToExclude = Gender.MALE;
    Gender randomElement = new EnumRandomizer<>(Gender.class, valueToExclude).getRandomValue();
    assertThat(randomElement).isNotNull();
    assertThat(randomElement).isNotEqualTo(valueToExclude);
}

@Deprecated
public static <E extends Enum<E>> EnumRandomizer<E> aNewEnumRandomizer(final Class<E> enumeration, final E... excludedValues) {
    return new EnumRandomizer<>(enumeration, excludedValues);
}

@Test
void generatedValueShouldBeOfTheSpecifiedEnum() {
    assertThat(aNewEnumRandomizer(Gender.class).getRandomValue()).isIn(Gender.values());
}

@Test
void generatedValueShouldBeOfTheSpecifiedEnum() {
    assertThat(new EnumRandomizer(Gender.class).getRandomValue()).isIn(Gender.values());
}

@Deprecated
public static <E extends Enum<E>> EnumRandomizer<E> aNewEnumRandomizer(final Class<E> enumeration) {
    return new EnumRandomizer<>(enumeration);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    assertThat(aNewEnumRandomizer(Gender.class, SEED).getRandomValue()).isEqualTo(FEMALE);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    assertThat(new EnumRandomizer(Gender.class, SEED).getRandomValue()).isEqualTo(FEMALE);
}

@Deprecated
public static <E extends Enum<E>> EnumRandomizer<E> aNewEnumRandomizer(final Class<E> enumeration, final long seed) {
    return new EnumRandomizer<>(enumeration, seed);
}

@Test
public void should_return_null_for_empty_enum() {
    Empty randomElement = aNewEnumRandomizer(Empty.class).getRandomValue();
    assertThat(randomElement).isNull();
}

@Test
public void should_return_null_for_empty_enum() {
    Empty randomElement = new EnumRandomizer<>(Empty.class).getRandomValue();
    assertThat(randomElement).isNull();
}


@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    BigDecimal javaVersion = new BigDecimal(System.getProperty("java.specification.version"));
    if (javaVersion.compareTo(new BigDecimal("14")) >= 0) {
        assertThat(aNewLocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("rn", "BI"));
    } else if (javaVersion.compareTo(new BigDecimal("13")) >= 0) {
        assertThat(aNewLocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("zh", "CN"));
    } else if (javaVersion.compareTo(new BigDecimal("11")) >= 0) {
        assertThat(aNewLocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CK"));
    } else if (javaVersion.compareTo(new BigDecimal("9")) >= 0) {
        assertThat(aNewLocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("sw", "ke"));
    } else {
        assertThat(aNewLocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("nl", "be"));
    }
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    BigDecimal javaVersion = new BigDecimal(System.getProperty("java.specification.version"));
    if (javaVersion.compareTo(new BigDecimal("14")) >= 0) {
        assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("rn", "BI"));
    } else if (javaVersion.compareTo(new BigDecimal("13")) >= 0) {
        assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("zh", "CN"));
    } else if (javaVersion.compareTo(new BigDecimal("11")) >= 0) {
        assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CK"));
    } else if (javaVersion.compareTo(new BigDecimal("9")) >= 0) {
        assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("sw", "ke"));
    } else {
        assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("nl", "be"));
    }
}

@Deprecated
public static LocaleRandomizer aNewLocaleRandomizer(final long seed) {
    return new LocaleRandomizer(seed);
}

@Test
void shouldGenerateRandomLocale() {
    assertThat(aNewLocaleRandomizer().getRandomValue()).isIn(Locale.getAvailableLocales());
}

@Test
void shouldGenerateRandomLocale() {
    assertThat(new LocaleRandomizer().getRandomValue()).isIn(Locale.getAvailableLocales());
}

@Deprecated
public static LocaleRandomizer aNewLocaleRandomizer() {
    return new LocaleRandomizer();
}


@Test
void generatedValueShouldBeNull() {
    assertThat(aNewNullRandomizer().getRandomValue()).isNull();
}

@Test
void generatedValueShouldBeNull() {
    assertThat(new NullRandomizer().getRandomValue()).isNull();
}

@Deprecated
public static NullRandomizer aNewNullRandomizer() {
    return new NullRandomizer();
}


@Test
void generatedValueShouldBeNull() {
    assertThat(aNewSkipRandomizer().getRandomValue()).isNull();
}

@Test
void generatedValueShouldBeNull() {
    assertThat(new SkipRandomizer().getRandomValue()).isNull();
}

@Deprecated
public static SkipRandomizer aNewSkipRandomizer() {
    return new SkipRandomizer();
}


@Test
void shouldGenerateRandomUUID() {
    assertThat(aNewUUIDRandomizer().getRandomValue()).isNotNull();
}

@Test
void shouldGenerateRandomUUID() {
    assertThat(new UUIDRandomizer().getRandomValue()).isNotNull();
}

@Deprecated
public static UUIDRandomizer aNewUUIDRandomizer() {
    return new UUIDRandomizer();
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    assertThat(aNewUUIDRandomizer(SEED).getRandomValue()).isEqualTo(new UUID(-5106534569952410475L, -167885730524958550L));
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    assertThat(new UUIDRandomizer(SEED).getRandomValue()).isEqualTo(new UUID(-5106534569952410475L, -167885730524958550L));
}

@Deprecated
public static UUIDRandomizer aNewUUIDRandomizer(final long seed) {
    return new UUIDRandomizer(seed);
}


@Test
void generateValueShouldNotBeNull() {
    AtomicIntegerRandomizer atomicIntegerRandomizer = aNewAtomicIntegerRandomizer();
    AtomicInteger atomicInteger = atomicIntegerRandomizer.getRandomValue();
    then(atomicInteger).isNotNull();
}

@Test
void generateValueShouldNotBeNull() {
    AtomicIntegerRandomizer atomicIntegerRandomizer = new AtomicIntegerRandomizer();
    AtomicInteger atomicInteger = atomicIntegerRandomizer.getRandomValue();
    then(atomicInteger).isNotNull();
}

@Deprecated
public static AtomicIntegerRandomizer aNewAtomicIntegerRandomizer() {
    return new AtomicIntegerRandomizer();
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    AtomicIntegerRandomizer atomicIntegerRandomizer = aNewAtomicIntegerRandomizer(SEED);
    AtomicInteger atomicInteger = atomicIntegerRandomizer.getRandomValue();
    then(atomicInteger).hasValue(-1188957731);
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    AtomicIntegerRandomizer atomicIntegerRandomizer = new AtomicIntegerRandomizer(SEED);
    AtomicInteger atomicInteger = atomicIntegerRandomizer.getRandomValue();
    then(atomicInteger).hasValue(-1188957731);
}

@Deprecated
public static AtomicIntegerRandomizer aNewAtomicIntegerRandomizer(final long seed) {
    return new AtomicIntegerRandomizer(seed);
}


@Test
void generateValueShouldNotBeNull() {
    AtomicLongRandomizer atomicLongRandomizer = aNewAtomicLongRandomizer();
    AtomicLong atomicLong = atomicLongRandomizer.getRandomValue();
    then(atomicLong).isNotNull();
}

@Test
void generateValueShouldNotBeNull() {
    AtomicLongRandomizer atomicLongRandomizer = new AtomicLongRandomizer();
    AtomicLong atomicLong = atomicLongRandomizer.getRandomValue();
    then(atomicLong).isNotNull();
}

@Deprecated
public static AtomicLongRandomizer aNewAtomicLongRandomizer() {
    return new AtomicLongRandomizer();
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    AtomicLongRandomizer atomicLongRandomizer = aNewAtomicLongRandomizer(SEED);
    AtomicLong atomicLong = atomicLongRandomizer.getRandomValue();
    then(atomicLong).hasValue(-5106534569952410475L);
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    AtomicLongRandomizer atomicLongRandomizer = new AtomicLongRandomizer(SEED);
    AtomicLong atomicLong = atomicLongRandomizer.getRandomValue();
    then(atomicLong).hasValue(-5106534569952410475L);
}

@Deprecated
public static AtomicLongRandomizer aNewAtomicLongRandomizer(final long seed) {
    return new AtomicLongRandomizer(seed);
}


@Test
void testCustomRoundingMode() {
    long initialSeed = 123;
    Integer scale = 1;
    RoundingMode roundingMode = RoundingMode.DOWN;
    BigDecimalRandomizer bigDecimalRandomizer = aNewBigDecimalRandomizer(initialSeed, scale, roundingMode);
    BigDecimal bigDecimal = bigDecimalRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("0.7"));
}

@Test
void testCustomRoundingMode() {
    long initialSeed = 123;
    Integer scale = 1;
    RoundingMode roundingMode = RoundingMode.DOWN;
    BigDecimalRandomizer bigDecimalRandomizer = new BigDecimalRandomizer(initialSeed, scale, roundingMode);
    BigDecimal bigDecimal = bigDecimalRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("0.7"));
}

@Deprecated
public static BigDecimalRandomizer aNewBigDecimalRandomizer(final long seed, final Integer scale, final RoundingMode roundingMode) {
    return new BigDecimalRandomizer(seed, scale, roundingMode);
}

@Test
void generatedValueShouldHaveProvidedPositiveScale() {
    Integer scale = 1;
    BigDecimalRandomizer bigDecimalRandomizer = aNewBigDecimalRandomizer(scale);
    BigDecimal bigDecimal = bigDecimalRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Test
void generatedValueShouldHaveProvidedPositiveScale() {
    Integer scale = 1;
    BigDecimalRandomizer bigDecimalRandomizer = new BigDecimalRandomizer(scale);
    BigDecimal bigDecimal = bigDecimalRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Deprecated
public static BigDecimalRandomizer aNewBigDecimalRandomizer(final Integer scale) {
    return new BigDecimalRandomizer(scale);
}

@Test
void generatedValueShouldHaveProvidedNegativeScale() {
    Integer scale = -1;
    BigDecimalRandomizer bigDecimalRangeRandomizer = aNewBigDecimalRandomizer(scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Test
void generatedValueShouldHaveProvidedNegativeScale() {
    Integer scale = -1;
    BigDecimalRandomizer bigDecimalRangeRandomizer = new BigDecimalRandomizer(scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = aNewBigDecimalRangeRandomizer(min, max, SEED);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("7.46393298637489266411648713983595371246337890625"));
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = new BigDecimalRangeRandomizer(min, max, SEED);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("7.46393298637489266411648713983595371246337890625"));
}

@Deprecated
public static BigDecimalRangeRandomizer aNewBigDecimalRangeRandomizer(final Double min, final Double max, final long seed) {
    return new BigDecimalRangeRandomizer(min, max, seed);
}

@Test
void generatedValueShouldHaveProvidedNegativeScale() {
    Integer scale = -2;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = aNewBigDecimalRangeRandomizer(min, max, scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Test
void generatedValueShouldHaveProvidedNegativeScale() {
    Integer scale = -2;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = new BigDecimalRangeRandomizer(min, max, scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Deprecated
public static BigDecimalRangeRandomizer aNewBigDecimalRangeRandomizer(final Double min, final Double max, final Integer scale) {
    return new BigDecimalRangeRandomizer(min, max, scale);
}

@Test
void generatedValueShouldHaveProvidedPositiveScale() {
    Integer scale = 2;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = aNewBigDecimalRangeRandomizer(min, max, scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Test
void generatedValueShouldHaveProvidedPositiveScale() {
    Integer scale = 2;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = new BigDecimalRangeRandomizer(min, max, scale);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal.scale()).isEqualTo(scale);
}

@Test
void testCustomRoundingMode() {
    Integer scale = 2;
    int seed = 123;
    RoundingMode roundingMode = RoundingMode.DOWN;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = aNewBigDecimalRangeRandomizer(min, max, seed, scale, roundingMode);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("7.46"));
}

@Test
void testCustomRoundingMode() {
    Integer scale = 2;
    int seed = 123;
    RoundingMode roundingMode = RoundingMode.DOWN;
    BigDecimalRangeRandomizer bigDecimalRangeRandomizer = new BigDecimalRangeRandomizer(min, max, seed, scale, roundingMode);
    BigDecimal bigDecimal = bigDecimalRangeRandomizer.getRandomValue();
    then(bigDecimal).isEqualTo(new BigDecimal("7.46"));
}

@Deprecated
public static BigDecimalRangeRandomizer aNewBigDecimalRangeRandomizer(final Double min, final Double max, final long seed, final Integer scale, final RoundingMode roundingMode) {
    return new BigDecimalRangeRandomizer(min, max, seed, scale, roundingMode);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    BigIntegerRangeRandomizer bigIntegerRangeRandomizer = aNewBigIntegerRangeRandomizer(min, max, SEED);
    BigInteger bigInteger = bigIntegerRangeRandomizer.getRandomValue();
    then(bigInteger).isEqualTo(new BigInteger("7"));
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    BigIntegerRangeRandomizer bigIntegerRangeRandomizer = new BigIntegerRangeRandomizer(min, max, SEED);
    BigInteger bigInteger = bigIntegerRangeRandomizer.getRandomValue();
    then(bigInteger).isEqualTo(new BigInteger("7"));
}

@Deprecated
public static BigIntegerRangeRandomizer aNewBigIntegerRangeRandomizer(final Integer min, final Integer max, final long seed) {
    return new BigIntegerRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    ByteRangeRandomizer byteRangeRandomizer = aNewByteRangeRandomizer(min, max, SEED);
    Byte b = byteRangeRandomizer.getRandomValue();
    then(b).isEqualTo((byte) 7);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    ByteRangeRandomizer byteRangeRandomizer = new ByteRangeRandomizer(min, max, SEED);
    Byte b = byteRangeRandomizer.getRandomValue();
    then(b).isEqualTo((byte) 7);
}

@Deprecated
public static ByteRangeRandomizer aNewByteRangeRandomizer(final Byte min, final Byte max, final long seed) {
    return new ByteRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    DoubleRangeRandomizer doubleRangeRandomizer = aNewDoubleRangeRandomizer(min, max, SEED);
    Double d = doubleRangeRandomizer.getRandomValue();
    then(d).isEqualTo(7.508567826974321);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    DoubleRangeRandomizer doubleRangeRandomizer = new DoubleRangeRandomizer(min, max, SEED);
    Double d = doubleRangeRandomizer.getRandomValue();
    then(d).isEqualTo(7.508567826974321);
}

@Deprecated
public static DoubleRangeRandomizer aNewDoubleRangeRandomizer(final Double min, final Double max, final long seed) {
    return new DoubleRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    FloatRangeRandomizer floatRangeRandomizer = aNewFloatRangeRandomizer(min, max, SEED);
    Float f = floatRangeRandomizer.getRandomValue();
    then(f).isEqualTo(0.0017231742f);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    FloatRangeRandomizer floatRangeRandomizer = new FloatRangeRandomizer(min, max, SEED);
    Float f = floatRangeRandomizer.getRandomValue();
    then(f).isEqualTo(0.0017231742f);
}

@Deprecated
public static FloatRangeRandomizer aNewFloatRangeRandomizer(final Float min, final Float max, final long seed) {
    return new FloatRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    IntegerRangeRandomizer integerRangeRandomizer = aNewIntegerRangeRandomizer(min, max, SEED);
    Integer i = integerRangeRandomizer.getRandomValue();
    then(i).isEqualTo(7);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    IntegerRangeRandomizer integerRangeRandomizer = new IntegerRangeRandomizer(min, max, SEED);
    Integer i = integerRangeRandomizer.getRandomValue();
    then(i).isEqualTo(7);
}

@Deprecated
public static IntegerRangeRandomizer aNewIntegerRangeRandomizer(final Integer min, final Integer max, final long seed) {
    return new IntegerRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    LongRangeRandomizer longRangeRandomizer = aNewLongRangeRandomizer(min, max, SEED);
    Long l = longRangeRandomizer.getRandomValue();
    then(l).isEqualTo(7L);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    LongRangeRandomizer longRangeRandomizer = new LongRangeRandomizer(min, max, SEED);
    Long l = longRangeRandomizer.getRandomValue();
    then(l).isEqualTo(7L);
}

@Deprecated
public static LongRangeRandomizer aNewLongRangeRandomizer(final Long min, final Long max, final long seed) {
    return new LongRangeRandomizer(min, max, seed);
}


@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    ShortRangeRandomizer shortRangeRandomizer = aNewShortRangeRandomizer(min, max, SEED);
    Short s = shortRangeRandomizer.getRandomValue();
    then(s).isEqualTo((short) 7);
}

@Test
void shouldAlwaysGenerateTheSameValueForTheSameSeed() {
    ShortRangeRandomizer shortRangeRandomizer = new ShortRangeRandomizer(min, max, SEED);
    Short s = shortRangeRandomizer.getRandomValue();
    then(s).isEqualTo((short) 7);
}

@Deprecated
public static ShortRangeRandomizer aNewShortRangeRandomizer(final Short min, final Short max, final long seed) {
    return new ShortRangeRandomizer(min, max, seed);
}


@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    TimeZone firstTimeZone = aNewTimeZoneRandomizer(SEED).getRandomValue();
    TimeZone secondTimeZone = aNewTimeZoneRandomizer(SEED).getRandomValue();
    assertThat(firstTimeZone).isNotNull();
    assertThat(secondTimeZone).isNotNull();
    assertThat(firstTimeZone).isEqualTo(secondTimeZone);
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    TimeZone firstTimeZone = new TimeZoneRandomizer(SEED).getRandomValue();
    TimeZone secondTimeZone = new TimeZoneRandomizer(SEED).getRandomValue();
    assertThat(firstTimeZone).isNotNull();
    assertThat(secondTimeZone).isNotNull();
    assertThat(firstTimeZone).isEqualTo(secondTimeZone);
}

@Deprecated
public static TimeZoneRandomizer aNewTimeZoneRandomizer(final long seed) {
    return new TimeZoneRandomizer(seed);
}


@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    ZoneId firstZoneId = aNewZoneIdRandomizer(SEED).getRandomValue();
    ZoneId secondZoneId = aNewZoneIdRandomizer(SEED).getRandomValue();
    assertThat(firstZoneId).isNotNull();
    assertThat(secondZoneId).isNotNull();
    assertThat(firstZoneId).isEqualTo(secondZoneId);
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    ZoneId firstZoneId = new ZoneIdRandomizer(SEED).getRandomValue();
    ZoneId secondZoneId = new ZoneIdRandomizer(SEED).getRandomValue();
    assertThat(firstZoneId).isNotNull();
    assertThat(secondZoneId).isNotNull();
    assertThat(firstZoneId).isEqualTo(secondZoneId);
}

@Deprecated
public static ZoneIdRandomizer aNewZoneIdRandomizer(final long seed) {
    return new ZoneIdRandomizer(seed);
}


@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    ZonedDateTimeRandomizer firstSeededZonedDateTimeRandomizer = aNewZonedDateTimeRandomizer(SEED);
    ZonedDateTimeRandomizer secondSeededZonedDateTimeRandomizer = aNewZonedDateTimeRandomizer(SEED);
    ZonedDateTime firstZonedTimeDate = firstSeededZonedDateTimeRandomizer.getRandomValue();
    ZonedDateTime secondZonedTimeDate = secondSeededZonedDateTimeRandomizer.getRandomValue();
    then(firstZonedTimeDate).isNotNull();
    then(secondZonedTimeDate).isNotNull();
    then(firstZonedTimeDate).isEqualTo(secondZonedTimeDate);
}

@Test
void shouldGenerateTheSameValueForTheSameSeed() {
    ZonedDateTimeRandomizer firstSeededZonedDateTimeRandomizer = new ZonedDateTimeRandomizer(SEED);
    ZonedDateTimeRandomizer secondSeededZonedDateTimeRandomizer = new ZonedDateTimeRandomizer(SEED);
    ZonedDateTime firstZonedTimeDate = firstSeededZonedDateTimeRandomizer.getRandomValue();
    ZonedDateTime secondZonedTimeDate = secondSeededZonedDateTimeRandomizer.getRandomValue();
    then(firstZonedTimeDate).isNotNull();
    then(secondZonedTimeDate).isNotNull();
    then(firstZonedTimeDate).isEqualTo(secondZonedTimeDate);
}

@Deprecated
public static ZonedDateTimeRandomizer aNewZonedDateTimeRandomizer(final long seed) {
    return new ZonedDateTimeRandomizer(seed);
}

@Test
void generatedValueShouldNotBeNull() {
    ZonedDateTimeRandomizer zonedDateTimeRandomizer = aNewZonedDateTimeRandomizer();
    ZonedDateTime zonedDateTime = zonedDateTimeRandomizer.getRandomValue();
    then(zonedDateTime).isNotNull();
}

@Test
void generatedValueShouldNotBeNull() {
    ZonedDateTimeRandomizer zonedDateTimeRandomizer = new ZonedDateTimeRandomizer();
    ZonedDateTime zonedDateTime = zonedDateTimeRandomizer.getRandomValue();
    then(zonedDateTime).isNotNull();
}

@Deprecated
public static ZonedDateTimeRandomizer aNewZonedDateTimeRandomizer() {
    return new ZonedDateTimeRandomizer();
}


@Test
void whenSafeModeIsDisabled_thenDomainNameShouldBeValid() {
    List<String> expectedDomains = Arrays.asList("gmail.com", "yahoo.com", "hotmail.com");
    EmailRandomizer emailRandomizer = EmailRandomizer.aNewEmailRandomizer(123L, Locale.ENGLISH, false);
    String randomValue = emailRandomizer.getRandomValue();
    String actualDomain = randomValue.substring(randomValue.lastIndexOf("@") + 1);
    assertThat(expectedDomains).contains(actualDomain);
}

@Test
void whenSafeModeIsDisabled_thenDomainNameShouldBeValid() {
    List<String> expectedDomains = Arrays.asList("gmail.com", "yahoo.com", "hotmail.com");
    EmailRandomizer emailRandomizer = new EmailRandomizer(123L, Locale.ENGLISH, false);
    String randomValue = emailRandomizer.getRandomValue();
    String actualDomain = randomValue.substring(randomValue.lastIndexOf("@") + 1);
    assertThat(expectedDomains).contains(actualDomain);
}

@Deprecated
public static EmailRandomizer aNewEmailRandomizer(final long seed, final Locale locale, final boolean safe) {
    return new EmailRandomizer(seed, locale, safe);
}

@Test
void whenSafeModeIsEnabled_thenDomainNameShouldBeInvalid() {
    EmailRandomizer emailRandomizer = EmailRandomizer.aNewEmailRandomizer(123L, Locale.ENGLISH, true);
    String randomValue = emailRandomizer.getRandomValue();
    assertThat(randomValue).contains("@example.com");
}

@Test
void whenSafeModeIsEnabled_thenDomainNameShouldBeInvalid() {
    EmailRandomizer emailRandomizer = new EmailRandomizer(123L, Locale.ENGLISH, true);
    String randomValue = emailRandomizer.getRandomValue();
    assertThat(randomValue).contains("@example.com");
}


@Test
public void testRuleDefinitionReading_withCompositeAndBasicRules() throws Exception {
    File compositeRuleDescriptor = new File("src/test/resources/composite-rule.yml");
    List<MVELRuleDefinition> ruleDefinitions = ruleDefinitionReader.readAll(new FileReader(compositeRuleDescriptor));
    assertThat(ruleDefinitions).hasSize(2);
    MVELRuleDefinition ruleDefinition = ruleDefinitions.get(0);
    assertThat(ruleDefinition).isNotNull();
    assertThat(ruleDefinition.getName()).isEqualTo("Movie id rule");
    assertThat(ruleDefinition.getDescription()).isEqualTo("description");
    assertThat(ruleDefinition.getCompositeRuleType()).isEqualTo("UnitRuleGroup");
    assertThat(ruleDefinition.getSubrules()).isNotEmpty();
    Rules subrules = ruleDefinition.getSubrules();
    assertThat(subrules).hasSize(2);
    Iterator<Rule> iterator = subrules.iterator();
    Rule subrule = iterator.next();
    assertThat(subrule.getName()).isEqualTo("Movie is rated R");
    assertThat(subrule.getDescription()).isEqualTo("If the movie is rated R");
    assertThat(subrule.getPriority()).isEqualTo(1);
    subrule = iterator.next();
    assertThat(subrule.getName()).isEqualTo("Time is evening");
    assertThat(subrule.getDescription()).isEqualTo("If it's later than 7pm");
    assertThat(subrule.getPriority()).isEqualTo(1);
    ruleDefinition = ruleDefinitions.get(1);
    assertThat(ruleDefinition).isNotNull();
    assertThat(ruleDefinition.getName()).isEqualTo("weather rule");
    assertThat(ruleDefinition.getDescription()).isEqualTo("when it rains, then take an umbrella");
    assertThat(ruleDefinition.getSubrules()).isNull();
    assertThat(ruleDefinition.getCondition()).isEqualTo("rain == True");
    assertThat(ruleDefinition.getActions()).isEqualTo(Collections.singletonList("System.out.println(\"It rains, take an umbrella!\");"));
}

@Test
public void testRuleDefinitionReading_withCompositeAndBasicRules() throws Exception {
    File compositeRuleDescriptor = new File("src/test/resources/composite-rule.yml");
    List<MVELRuleDefinition> ruleDefinitions = ruleDefinitionReader.readAll(new FileReader(compositeRuleDescriptor));
    assertThat(ruleDefinitions).hasSize(2);
    MVELRuleDefinition ruleDefinition = ruleDefinitions.get(0);
    assertThat(ruleDefinition).isNotNull();
    assertThat(ruleDefinition.getName()).isEqualTo("Movie id rule");
    assertThat(ruleDefinition.getDescription()).isEqualTo("description");
    assertThat(ruleDefinition.getCompositeRuleType()).isEqualTo("UnitRuleGroup");
    assertThat(ruleDefinition.getComposingRules()).isNotEmpty();
    Rules subrules = ruleDefinition.getComposingRules();
    assertThat(subrules).hasSize(2);
    Iterator<Rule> iterator = subrules.iterator();
    Rule subrule = iterator.next();
    assertThat(subrule.getName()).isEqualTo("Movie is rated R");
    assertThat(subrule.getDescription()).isEqualTo("If the movie is rated R");
    assertThat(subrule.getPriority()).isEqualTo(1);
    subrule = iterator.next();
    assertThat(subrule.getName()).isEqualTo("Time is evening");
    assertThat(subrule.getDescription()).isEqualTo("If it's later than 7pm");
    assertThat(subrule.getPriority()).isEqualTo(1);
    ruleDefinition = ruleDefinitions.get(1);
    assertThat(ruleDefinition).isNotNull();
    assertThat(ruleDefinition.getName()).isEqualTo("weather rule");
    assertThat(ruleDefinition.getDescription()).isEqualTo("when it rains, then take an umbrella");
    assertThat(ruleDefinition.getComposingRules()).isNull();
    assertThat(ruleDefinition.getCondition()).isEqualTo("rain == True");
    assertThat(ruleDefinition.getActions()).isEqualTo(Collections.singletonList("System.out.println(\"It rains, take an umbrella!\");"));
}

Rules getComposingRules() {
    return composingRules;
}


@Test
public void testRuleDefinitionReadingFromString() throws Exception {
    String adultRuleDescriptor = new String(Files.readAllBytes(Paths.get("src/test/resources/adult-rule.yml")));
    MVELRuleDefinition adultRuleDefinition = ruleDefinitionReader.read(adultRuleDescriptor);
    assertThat(adultRuleDefinition).isNotNull();
    assertThat(adultRuleDefinition.getName()).isEqualTo("adult rule");
    assertThat(adultRuleDefinition.getDescription()).isEqualTo("when age is greater then 18, then mark as adult");
    assertThat(adultRuleDefinition.getPriority()).isEqualTo(1);
    assertThat(adultRuleDefinition.getCondition()).isEqualTo("person.age > 18");
    assertThat(adultRuleDefinition.getActions()).isEqualTo(Collections.singletonList("person.setAdult(true);"));
}

@Test
public void testRuleDefinitionReadingFromString() throws Exception {
    String adultRuleDescriptor = new String(Files.readAllBytes(Paths.get("src/test/resources/adult-rule.yml")));
    MVELRuleDefinition adultRuleDefinition = ruleDefinitionReader.read(new StringReader(adultRuleDescriptor));
    assertThat(adultRuleDefinition).isNotNull();
    assertThat(adultRuleDefinition.getName()).isEqualTo("adult rule");
    assertThat(adultRuleDefinition.getDescription()).isEqualTo("when age is greater then 18, then mark as adult");
    assertThat(adultRuleDefinition.getPriority()).isEqualTo(1);
    assertThat(adultRuleDefinition.getCondition()).isEqualTo("person.age > 18");
    assertThat(adultRuleDefinition.getActions()).isEqualTo(Collections.singletonList("person.setAdult(true);"));
}

MVELRuleDefinition read(String descriptor) {
    return read(new StringReader(descriptor));
}


@Test
public void annotationsShouldBeInherited() throws Exception {
    MyChildRule myChildRule = new MyChildRule();
    RulesEngine rulesEngine = RulesEngineBuilder.aNewRulesEngine().build();
    rulesEngine.registerRule(myChildRule);
    rulesEngine.fireRules();
    assertThat(myChildRule.isExecuted()).isTrue();
}

@Test
public void annotationsShouldBeInherited() throws Exception {
    MyChildRule myChildRule = new MyChildRule();
    RulesEngine rulesEngine = RulesEngineBuilder.aNewRulesEngine().build();
    Rules rules = new Rules();
    rules.register(myChildRule);
    rulesEngine.fire(rules, new Facts());
    assertThat(myChildRule.isExecuted()).isTrue();
}

@Override
public void fire(Rules rules, Facts facts) {
    if (rules.isEmpty()) {
        LOGGER.warning("No rules registered! Nothing to apply");
        return;
    }
    sort(rules);
    logEngineParameters();
    log(rules, facts);
    apply(rules, facts);
}


@Test
public void basicRuleEvaluateShouldReturnFalse() throws Exception {
    BasicRule basicRule = new BasicRule();
    assertThat(basicRule.evaluate()).isFalse();
}

@Test
public void basicRuleEvaluateShouldReturnFalse() throws Exception {
    BasicRule basicRule = new BasicRule();
    assertThat(basicRule.evaluate(facts)).isFalse();
}

public boolean evaluate() {
    return false;
}

public boolean evaluate(Facts facts) {
    return false;
}

@Test
public void testSortSequence() {
    FirstRule rule1 = new FirstRule();
    SecondRule rule2 = new SecondRule();
    ThirdRule rule3 = new ThirdRule();
    rulesEngine.registerRule(rule1);
    rulesEngine.registerRule(rule2);
    rulesEngine.registerRule(rule3);
    rulesEngine.checkRules();
    Set<Rule> theRules = rulesEngine.getRules();
    assertThat(theRules).containsSequence(rule1, rule3, rule2);
}

@Test
public void testSortSequence() {
    FirstRule rule1 = new FirstRule();
    SecondRule rule2 = new SecondRule();
    ThirdRule rule3 = new ThirdRule();
    rules = new Rules(rule1, rule2, rule3);
    rulesEngine.check(rules, facts);
    assertThat(rules).containsSequence(rule1, rule3, rule2);
}

@Override
public Map<Rule, Boolean> check(Rules rules, Facts facts) {
    LOGGER.info("Checking rules");
    sort(rules);
    Map<Rule, Boolean> result = new HashMap<>();
    for (Rule rule : rules) {
        if (shouldBeEvaluated(rule, facts)) {
            result.put(rule, rule.evaluate(facts));
        }
    }
    return result;
}


@Test
public void compositeRuleMustNotBeExecutedIfAComposingRuleEvaluatesToFalse() throws Exception {
    when(rule2.evaluate()).thenReturn(false);
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    rulesEngine.registerRule(compositeRule);
    rulesEngine.fireRules();
    verify(rule1, never()).execute();
    verify(rule2, never()).execute();
}

@Test
public void compositeRuleMustNotBeExecutedIfAComposingRuleEvaluatesToFalse() throws Exception {
    when(rule2.evaluate(facts)).thenReturn(false);
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    rules.clear();
    rules.register(compositeRule);
    rulesEngine.fire(rules, facts);
    verify(rule1, never()).execute(facts);
    verify(rule2, never()).execute(facts);
}

@Test
public void whenARuleIsRemoved_thenItShouldNotBeEvaluated() throws Exception {
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    compositeRule.removeRule(rule2);
    rulesEngine.registerRule(compositeRule);
    rulesEngine.fireRules();
    verify(rule1).execute();
    verify(rule2, never()).evaluate();
    verify(rule2, never()).execute();
}

@Test
public void whenARuleIsRemoved_thenItShouldNotBeEvaluated() throws Exception {
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    compositeRule.removeRule(rule2);
    rules.clear();
    rules.register(compositeRule);
    rulesEngine.fire(rules, facts);
    verify(rule1).execute(facts);
    verify(rule2, never()).evaluate(facts);
    verify(rule2, never()).execute(facts);
}

@Test
public void whenNoComposingRulesAreRegistered_thenCompositeRuleShouldEvaluateToFalse() {
    assertThat(compositeRule.evaluate()).isFalse();
}

@Test
public void whenNoComposingRulesAreRegistered_thenCompositeRuleShouldEvaluateToFalse() {
    assertThat(compositeRule.evaluate(facts)).isFalse();
}

@Override
public boolean evaluate() {
    if (!rules.isEmpty()) {
        for (Rule rule : rules) {
            if (!rule.evaluate()) {
                return false;
            }
        }
        return true;
    }
    return false;
}

@Override
public boolean evaluate(Facts facts) {
    if (!rules.isEmpty()) {
        for (Rule rule : rules) {
            if (!rule.evaluate(facts)) {
                return false;
            }
        }
        return true;
    }
    return false;
}

@Test
public void testCompositeRuleWithAnnotatedComposingRules() throws Exception {
    MyRule rule = new MyRule();
    compositeRule = new CompositeRule("myCompositeRule");
    compositeRule.addRule(rule);
    rulesEngine.registerRule(compositeRule);
    rulesEngine.fireRules();
    assertThat(rule.isExecuted()).isTrue();
}

@Test
public void testCompositeRuleWithAnnotatedComposingRules() throws Exception {
    MyRule rule = new MyRule();
    compositeRule = new CompositeRule("myCompositeRule");
    compositeRule.addRule(rule);
    rules.register(compositeRule);
    ;
    rulesEngine.fire(rules, facts);
    assertThat(rule.isExecuted()).isTrue();
}

@Override
public void fireRules() {
    if (rules.isEmpty()) {
        LOGGER.warning("No rules registered! Nothing to apply");
        return;
    }
    logEngineParameters();
    sortRules();
    logRegisteredRules();
    applyRules();
}

@Test
public void compositeRuleAndComposingRulesMustBeExecuted() throws Exception {
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    rulesEngine.registerRule(compositeRule);
    rulesEngine.fireRules();
    verify(rule1).execute();
    verify(rule2).execute();
}

@Test
public void compositeRuleAndComposingRulesMustBeExecuted() throws Exception {
    compositeRule.addRule(rule1);
    compositeRule.addRule(rule2);
    rules.clear();
    rules.register(compositeRule);
    rulesEngine.fire(rules, facts);
    verify(rule1).execute(facts);
    verify(rule2).execute(facts);
}

public void clear() {
    rules.clear();
}

@Test
public void whenAnnotatedRuleIsRemoved_thenItsProxyShouldBeRetrieved() throws Exception {
    MyRule rule = new MyRule();
    MyAnnotatedRule annotatedRule = new MyAnnotatedRule();
    compositeRule = new CompositeRule("myCompositeRule", "composite rule with mixed types of rules");
    compositeRule.addRule(rule);
    compositeRule.addRule(annotatedRule);
    compositeRule.removeRule(annotatedRule);
    rulesEngine.registerRule(compositeRule);
    rulesEngine.fireRules();
    assertThat(rule.isExecuted()).isTrue();
    assertThat(annotatedRule.isExecuted()).isFalse();
}

@Test
public void whenAnnotatedRuleIsRemoved_thenItsProxyShouldBeRetrieved() throws Exception {
    MyRule rule = new MyRule();
    MyAnnotatedRule annotatedRule = new MyAnnotatedRule();
    compositeRule = new CompositeRule("myCompositeRule", "composite rule with mixed types of rules");
    compositeRule.addRule(rule);
    compositeRule.addRule(annotatedRule);
    compositeRule.removeRule(annotatedRule);
    rules.register(compositeRule);
    rulesEngine.fire(rules, facts);
    assertThat(rule.isExecuted()).isTrue();
    assertThat(annotatedRule.isExecuted()).isFalse();
}