Skip to content

Commit

Permalink
Add error state coverage
Browse files Browse the repository at this point in the history
This is probably incomplete, but that's okay
  • Loading branch information
softwaresale committed Jun 7, 2024
1 parent 4f2c0e1 commit f0f0a20
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 17 deletions.
104 changes: 92 additions & 12 deletions src/main/java/dk/brics/automaton/AutomatonCoverage.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@

public class AutomatonCoverage {

public static final int FAILURE_STATE_ID = -1;

public static final class Edge {
private final int leftStateId;
private final int rightStateId;

public Edge(int leftStateId, int rightStateId) {
this.leftStateId = leftStateId;
this.rightStateId = rightStateId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edge edge = (Edge) o;
return leftStateId == edge.leftStateId && rightStateId == edge.rightStateId;
}

@Override
public int hashCode() {
return Objects.hash(leftStateId, rightStateId);
}

public int getLeftStateId() {
return leftStateId;
}

public int getRightStateId() {
return rightStateId;
}
}

public static final class EdgePair {
private final int left;
private final int middle;
Expand Down Expand Up @@ -43,14 +76,14 @@ public int hashCode() {

public static final class VisitationInfo {
private final Set<Integer> visitedNodes;
private final Set<Integer> visitedEdges;
private final Set<Edge> visitedEdges;
private final Set<EdgePair> visitedEdgePairs;

public VisitationInfo() {
this(new HashSet<>(), new HashSet<>(), new HashSet<>());
}

public VisitationInfo(Set<Integer> visitedNodes, Set<Integer> visitedEdges, Set<EdgePair> visitedEdgePairs) {
public VisitationInfo(Set<Integer> visitedNodes, Set<Edge> visitedEdges, Set<EdgePair> visitedEdgePairs) {
this.visitedNodes = visitedNodes;
this.visitedEdges = visitedEdges;
this.visitedEdgePairs = visitedEdgePairs;
Expand All @@ -60,7 +93,7 @@ public Set<Integer> getVisitedNodes() {
return visitedNodes;
}

public Set<Integer> getVisitedEdges() {
public Set<Edge> getVisitedEdges() {
return visitedEdges;
}

Expand All @@ -72,7 +105,7 @@ private void addVisitedNode(int node) {
visitedNodes.add(node);
}

public void addVisitedEdge(int edge) {
public void addVisitedEdge(Edge edge) {
visitedEdges.add(edge);
}

Expand All @@ -89,7 +122,7 @@ public void foldIn(VisitationInfo other) {
public VisitationInfo combineWith(VisitationInfo other) {
Set<Integer> combinedVisitedNodes = new HashSet<>(this.visitedNodes);
combinedVisitedNodes.addAll(other.visitedNodes);
Set<Integer> combinedVisitedEdges = new HashSet<>(this.visitedEdges);
Set<Edge> combinedVisitedEdges = new HashSet<>(this.visitedEdges);
combinedVisitedEdges.addAll(other.visitedEdges);
Set<EdgePair> combinedVisitedEdgePairs = new HashSet<>(this.visitedEdgePairs);
combinedVisitedEdgePairs.addAll(other.visitedEdgePairs);
Expand All @@ -101,6 +134,28 @@ public VisitationInfo combineWith(VisitationInfo other) {
);
}

/**
* Visit everything at once.
* @param previousState The state we were at
* @param currentState The state we are current on
* @param nextState The state we are about to go to
*/
public void visitConfiguration(Integer previousState, int currentState, Integer nextState) {
// visit the center state
addVisitedNode(currentState);

// add an edge if needed
if (previousState == null) {
return;
}

addVisitedEdge(new Edge(previousState, currentState));

if (nextState != null) {
addVisitedEdgePair(new EdgePair(previousState, currentState, nextState));
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down Expand Up @@ -162,7 +217,6 @@ public double getEdgePairCoverage() {
public AutomatonCoverage(Automaton automaton) {
this.originalAutomaton = automaton;
this.runAutomaton = new RunAutomaton(automaton);

this.transitionTable = new TransitionTable(automaton);

fullMatchVisitationInfo = new VisitationInfo();
Expand All @@ -183,9 +237,9 @@ public VisitationInfoSummary getPartialMatchVisitationInfoSummary() {
}

private VisitationInfoSummary summarizeVisitationInfo(VisitationInfo info) {
double nodeCoverage = (double) info.getVisitedNodes().size() / originalAutomaton.getLiveStates().size();
double edgeCoverage = (double) info.getVisitedEdges().size() / originalAutomaton.getNumberOfTransitions();
double edgePairCoverage = (double) info.getVisitedEdgePairs().size() / transitionTable.countPossibleEdgePairs();
double nodeCoverage = (double) info.getVisitedNodes().size() / computeNumberOfStates();
double edgeCoverage = (double) info.getVisitedEdges().size() / computeNumberOfEdges();
double edgePairCoverage = (double) info.getVisitedEdgePairs().size() / computeNumberOfEdgePairs();

return new VisitationInfoSummary(nodeCoverage, edgeCoverage, edgePairCoverage);
}
Expand All @@ -210,6 +264,14 @@ private VisitationInfo evaluateString(String input, boolean fullMatch) {
char transitionCharacter = input.charAt(currentPos);
int nextState = this.runAutomaton.step(stateCursor, transitionCharacter);
if (nextState == -1) {

// we have encountered a failure state/edge
visitationInfo.addVisitedEdge(new Edge(stateCursor, FAILURE_STATE_ID));

// add an edge pair as well
int finalStateCursor2 = stateCursor;
previousState.ifPresent(prevState -> visitationInfo.addVisitedEdgePair(new EdgePair(prevState, finalStateCursor2, FAILURE_STATE_ID)));

// there's no outgoing state, then there are two things we can try:
if (fullMatch) {
// if we're in full match mode, then we're done
Expand All @@ -223,9 +285,9 @@ private VisitationInfo evaluateString(String input, boolean fullMatch) {
// We moved to another state, so that state should be marked as visited
visitationInfo.addVisitedNode(nextState);

// Find an edge between the two states and mark it as visited
Optional<Transition> takenEdge = transitionTable.findEdgeBetweenStates(stateCursor, nextState, transitionCharacter);
takenEdge.ifPresent(transition -> visitationInfo.addVisitedEdge(transition.getId()));
// construct an edge with our current state info
int finalStateCursor1 = stateCursor;
previousState.ifPresent(prevState -> visitationInfo.addVisitedEdge(new Edge(prevState, finalStateCursor1)));

// record edge pair if possible
int finalStateCursor = stateCursor;
Expand All @@ -241,4 +303,22 @@ private VisitationInfo evaluateString(String input, boolean fullMatch) {

return visitationInfo;
}

private int computeNumberOfStates() {
// add one for the error state
return originalAutomaton.getLiveStates().size() + 1;
}

private int computeNumberOfEdges() {
// all edges + an edge from each state to the failure state
return originalAutomaton.getNumberOfTransitions() + originalAutomaton.getNumberOfStates();
}

/**
* TODO figure out how to compute this without using the transition table
* @return Number of possible edge pairs
*/
private int computeNumberOfEdgePairs() {
return transitionTable.countPossibleEdgePairs() + originalAutomaton.getNumberOfTransitions();
}
}
10 changes: 5 additions & 5 deletions src/test/java/dk/brics/automaton/AutomatonCoverageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ void pattern1_coverage_shouldHaveFullCoverage() throws DfaTooLargeException {
coverage.evaluate("a");
coverage.evaluate("b");
coverage.evaluate("c");
AutomatonCoverage.VisitationInfo info = coverage.getVisitationInfo();
AutomatonCoverage.VisitationInfo info = coverage.getFullMatchVisitationInfo();

Set<Integer> liveStates = statesToStateNums(auto.getLiveStates());

Expand All @@ -33,7 +33,7 @@ void pattern2_coverage_shouldHaveFullCoverage() throws DfaTooLargeException {

coverage.evaluate("abd");

AutomatonCoverage.VisitationInfo info = coverage.getVisitationInfo();
AutomatonCoverage.VisitationInfo info = coverage.getFullMatchVisitationInfo();
assertThat(info.getVisitedNodes()).containsExactlyInAnyOrderElementsOf(statesToStateNums(auto.getLiveStates()));
assertThat(info.getVisitedEdges().size()).isEqualTo(auto.getNumberOfTransitions());
}
Expand All @@ -46,7 +46,7 @@ void pattern3_coverage_hasIncompleteNodeCoverage() throws DfaTooLargeException {

coverage.evaluate("http://");

AutomatonCoverage.VisitationInfo info = coverage.getVisitationInfo();
AutomatonCoverage.VisitationInfo info = coverage.getFullMatchVisitationInfo();

Set<Integer> states = statesToStateNums(auto.getLiveStates());
states.remove(4);
Expand All @@ -61,7 +61,7 @@ void pattern3_coverage_hasIncompleteEdgeCoverage() throws DfaTooLargeException {

coverage.evaluate("https://");

AutomatonCoverage.VisitationInfo info = coverage.getVisitationInfo();
AutomatonCoverage.VisitationInfo info = coverage.getFullMatchVisitationInfo();

Set<Integer> states = statesToStateNums(auto.getLiveStates());
assertThat(info.getVisitedNodes()).containsExactlyInAnyOrderElementsOf(states);
Expand All @@ -77,7 +77,7 @@ void pattern3_coverage_hasCompleteCoverage() throws DfaTooLargeException {
coverage.evaluate("https://");
coverage.evaluate("http://");

AutomatonCoverage.VisitationInfo info = coverage.getVisitationInfo();
AutomatonCoverage.VisitationInfo info = coverage.getFullMatchVisitationInfo();

assertThat(info.getVisitedNodes()).containsExactlyInAnyOrderElementsOf(statesToStateNums(auto.getLiveStates()));
assertThat(info.getVisitedEdges().size()).isEqualTo(auto.getNumberOfTransitions());
Expand Down

0 comments on commit f0f0a20

Please sign in to comment.