Skip to content

Commit

Permalink
TestKit: improve url selection for random walker #433
Browse files Browse the repository at this point in the history
  • Loading branch information
remmeier committed Nov 4, 2019
1 parent e39dc01 commit adaf582
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 21 deletions.
6 changes: 3 additions & 3 deletions crnk-meta/src/test/java/io/crnk/meta/MetaMetaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ public void testMetaDataObjectMeta() {
MetaAttribute elementTypeAttr = meta.getAttribute("elementType");
Assert.assertNotNull(elementTypeAttr);
Assert.assertNotNull(elementTypeAttr.getType());
Assert.assertEquals("resources.meta.type.elementType", elementTypeAttr.getId());
Assert.assertEquals("resources.metaType.elementType", elementTypeAttr.getId());

MetaAttribute attrsAttr = meta.getAttribute("attributes");
Assert.assertNotNull(attrsAttr.getType());

MetaAttribute childrenAttr = meta.getAttribute("children");
Assert.assertEquals("resources.meta.element.children", childrenAttr.getId());
Assert.assertEquals("resources.meta.element$list", childrenAttr.getType().getId());
Assert.assertEquals("resources.metaElement.children", childrenAttr.getId());
Assert.assertEquals("resources.metaElement$list", childrenAttr.getType().getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.util.Random;
import java.util.Set;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand All @@ -18,11 +17,12 @@
import io.crnk.client.http.HttpAdapterRequest;
import io.crnk.client.http.HttpAdapterResponse;
import io.crnk.core.engine.http.HttpMethod;
import io.crnk.core.engine.internal.utils.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Performs a random walk across an API endpoint by following JSON:API links.
* Performs a bounded random walk across an API endpoint by following JSON:API links.
*/
public class RandomWalkLinkChecker {

Expand All @@ -32,6 +32,8 @@ public class RandomWalkLinkChecker {

private int walkLength = 1000;

private Random random = new Random();

private Set<String> visited = new HashSet<>();

private List<String> upcoming = new ArrayList<>();
Expand All @@ -40,6 +42,12 @@ public class RandomWalkLinkChecker {

private String currentUrl;

private List<Predicate<String>> blackListPredicates = new ArrayList<>();

private List<String> previousVisits = new ArrayList<>();

private int maxPreviousVisitHistorySize = 30;

public RandomWalkLinkChecker(HttpAdapter httpAdapter) {
this.httpAdapter = httpAdapter;
}
Expand All @@ -56,25 +64,63 @@ public int getWalkLength() {
return walkLength;
}

private String greatestCommonPrefix(String a, String b) {
int minLength = Math.min(a.length(), b.length());
for (int i = 0; i < minLength; i++) {
if (a.charAt(i) != b.charAt(i)) {
return a.substring(0, i);
}
}
return a.substring(0, minLength);
}

public Set<String> performCheck() {
Random random = new Random();


int index = 0;
while (index < walkLength && !upcoming.isEmpty()) {

int nextIndex = random.nextInt(upcoming.size());
int nextIndex = selectNext();

currentUrl = upcoming.remove(nextIndex);
if (!visited.contains(currentUrl)) {
visited.add(currentUrl);
index++;
visit(currentUrl);

previousVisits.add(currentUrl);
while (previousVisits.size() > maxPreviousVisitHistorySize) {
previousVisits.remove(0);
}
}
}
return visited;
}

private void visit(String url) {
/**
* @return select next url to visit from the upcoming queue. We attempt to choose an url that is most different to any other previously visited urls.
*/
protected int selectNext() {
// consider moving to a prefix tree to better distribute testing evenly across all repositories
int numCandidates = 100;
int nextIndex = -1;
int nextPriority = Integer.MAX_VALUE;
for (int i = 0; i < numCandidates; i++) {
int candidateIndex = random.nextInt(upcoming.size());
String candidateUrl = upcoming.get(candidateIndex);
int candidatePriority = 0;
for (String previousVisit : previousVisits) {
candidatePriority += greatestCommonPrefix(previousVisit, candidateUrl).length();
}
if (candidatePriority < nextPriority) {
nextIndex = candidateIndex;
nextPriority = candidatePriority;
}
}
return nextIndex;
}

protected void visit(String url) {
try {
LOGGER.info("visiting {}", url);
HttpAdapterRequest request = httpAdapter.newRequest(url, HttpMethod.GET, null);
Expand All @@ -95,7 +141,7 @@ private void visit(String url) {
}
}

private void findLinks(JsonNode jsonNode) {
protected void findLinks(JsonNode jsonNode) {
if (jsonNode instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) jsonNode;
for (int i = 0; i < arrayNode.size(); i++) {
Expand All @@ -110,7 +156,7 @@ private void findLinks(JsonNode jsonNode) {
String name = entry.getKey();
if (name.equals("links")) {
JsonNode linksValue = entry.getValue();
if(!(linksValue instanceof ObjectNode)){
if (!(linksValue instanceof ObjectNode)) {
throw new IllegalStateException("illegal use of links field in " + currentUrl + ": " + linksValue);
}
collectLinks((ObjectNode) linksValue);
Expand All @@ -121,23 +167,26 @@ private void findLinks(JsonNode jsonNode) {
}
}

private void collectLinks(ObjectNode linksNode) {
protected void collectLinks(ObjectNode linksNode) {
Iterator<Map.Entry<String, JsonNode>> iterator = linksNode.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
JsonNode link = entry.getValue();
String url = link.asText();
if (url == null || !url.startsWith("http")) {
try {
String linkName = entry.getKey();
throw new IllegalStateException(
"expected link `" + linkName + "` from " + currentUrl + " to contain a valid link, got " + url + " from " + mapper.writer().writeValueAsString(linksNode));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
} else if (!visited.contains(url)) {
upcoming.add(url);
if (url != null && url.startsWith("http") && !visited.contains(url)) {
queueLink(url);
}
}
}

public void addBlackListPredicate(Predicate<String> blackListPredicate) {
this.blackListPredicates.add(blackListPredicate);
}

protected void queueLink(String url) {
boolean blacklisted = blackListPredicates.stream().anyMatch(it -> it.test(url));
if (!blacklisted) {
upcoming.add(url);
}
}
}

0 comments on commit adaf582

Please sign in to comment.