blob: 0424e10dc8607b1694721303f263f3e3dae2b144 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ahat;
import com.android.ahat.dominators.DominatorsComputation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class DominatorsTest {
private static class Node implements DominatorsComputation.Node {
public String name;
public List<Node> depends = new ArrayList<Node>();
public Node dominator;
private Object dominatorsComputationState;
public Node(String name) {
this.name = name;
}
public void computeDominators() {
DominatorsComputation.computeDominators(this);
}
public String toString() {
return name;
}
@Override
public void setDominatorsComputationState(Object state) {
dominatorsComputationState = state;
}
@Override
public Object getDominatorsComputationState() {
return dominatorsComputationState;
}
@Override
public Collection<Node> getReferencesForDominators() {
return depends;
}
@Override
public void setDominator(DominatorsComputation.Node dominator) {
this.dominator = (Node)dominator;
}
}
@Test
public void singleNode() {
// --> n
// Trivial case.
Node n = new Node("n");
n.computeDominators();
}
@Test
public void parentWithChild() {
// --> parent --> child
// The child node is dominated by the parent.
Node parent = new Node("parent");
Node child = new Node("child");
parent.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, child.dominator);
}
@Test
public void reachableTwoWays() {
// /-> right -->\
// --> parent child
// \-> left --->/
// The child node can be reached either by right or by left.
Node parent = new Node("parent");
Node right = new Node("right");
Node left = new Node("left");
Node child = new Node("child");
parent.depends = Arrays.asList(left, right);
right.depends = Arrays.asList(child);
left.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, left.dominator);
assertEquals(parent, right.dominator);
assertEquals(parent, child.dominator);
}
@Test
public void reachableDirectAndIndirect() {
// /-> right -->\
// --> parent -----------> child
// The child node can be reached either by right or parent.
Node parent = new Node("parent");
Node right = new Node("right");
Node child = new Node("child");
parent.depends = Arrays.asList(right, child);
right.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, child.dominator);
assertEquals(parent, right.dominator);
}
@Test
public void subDominator() {
// --> parent --> middle --> child
// The child is dominated by an internal node.
Node parent = new Node("parent");
Node middle = new Node("middle");
Node child = new Node("child");
parent.depends = Arrays.asList(middle);
middle.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, middle.dominator);
assertEquals(middle, child.dominator);
}
@Test
public void childSelfLoop() {
// --> parent --> child -\
// \<---/
// The child points back to itself.
Node parent = new Node("parent");
Node child = new Node("child");
parent.depends = Arrays.asList(child);
child.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, child.dominator);
}
@Test
public void singleEntryLoop() {
// --> parent --> a --> b --> c -\
// \<------------/
// There is a loop in the graph, with only one way into the loop.
Node parent = new Node("parent");
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
parent.depends = Arrays.asList(a);
a.depends = Arrays.asList(b);
b.depends = Arrays.asList(c);
c.depends = Arrays.asList(a);
parent.computeDominators();
assertEquals(parent, a.dominator);
assertEquals(a, b.dominator);
assertEquals(b, c.dominator);
}
@Test
public void multiEntryLoop() {
// --> parent --> right --> a --> b ----\
// \ \<-- c <---/
// \--> left --->--------/
// There is a loop in the graph, with two different ways to enter the
// loop.
Node parent = new Node("parent");
Node left = new Node("left");
Node right = new Node("right");
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
parent.depends = Arrays.asList(left, right);
right.depends = Arrays.asList(a);
left.depends = Arrays.asList(c);
a.depends = Arrays.asList(b);
b.depends = Arrays.asList(c);
c.depends = Arrays.asList(a);
parent.computeDominators();
assertEquals(parent, right.dominator);
assertEquals(parent, left.dominator);
assertEquals(parent, a.dominator);
assertEquals(parent, c.dominator);
assertEquals(a, b.dominator);
}
@Test
public void dominatorOverwrite() {
// /---------> right <--\
// --> parent --> child <--/ /
// \---> left ---------/
// Test a strange case where we have had trouble in the past with a
// dominator getting improperly overwritten. The relevant features of this
// case are: 'child' is visited after 'right', 'child' is dominated by
// 'parent', and 'parent' revisits 'right' after visiting 'child'.
Node parent = new Node("parent");
Node right = new Node("right");
Node left = new Node("left");
Node child = new Node("child");
parent.depends = Arrays.asList(left, child, right);
left.depends = Arrays.asList(right);
right.depends = Arrays.asList(child);
parent.computeDominators();
assertEquals(parent, left.dominator);
assertEquals(parent, child.dominator);
assertEquals(parent, right.dominator);
}
@Test
public void stackOverflow() {
// --> a --> b --> ... --> N
// Verify we don't smash the stack for deep chains.
Node root = new Node("root");
Node curr = root;
for (int i = 0; i < 10000; ++i) {
Node node = new Node("n" + i);
curr.depends.add(node);
curr = node;
}
root.computeDominators();
}
@Test
public void hiddenRevisit() {
// /-> left ---->---------\
// --> parent \---> a --> b --> c
// \-> right -/
// Test a case we have had trouble in the past.
// When a's dominator is updated from left to parent, that should trigger
// all reachable children's dominators to be updated too. In particular,
// c's dominator should be updated, even though b's dominator is
// unchanged.
Node parent = new Node("parent");
Node right = new Node("right");
Node left = new Node("left");
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
parent.depends = Arrays.asList(right, left);
left.depends = Arrays.asList(a, c);
right.depends = Arrays.asList(a);
a.depends = Arrays.asList(b);
b.depends = Arrays.asList(c);
parent.computeDominators();
assertEquals(parent, left.dominator);
assertEquals(parent, right.dominator);
assertEquals(parent, a.dominator);
assertEquals(parent, c.dominator);
assertEquals(a, b.dominator);
}
@Test
public void preUndominatedUpdate() {
// /--------->--------\
// / /---->----\
// --> p -> a --> b --> c --> d --> e
// \---------->----------/
// Test a case we have had trouble in the past.
// The candidate dominator for e is revised from d to a, then d is shown
// to be reachable from p. Make sure that causes e's dominator to be
// refined again from a to p. The extra nodes are there to ensure the
// necessary scheduling to expose the bug we had.
Node p = new Node("p");
Node a = new Node("a");
Node b = new Node("b");
Node c = new Node("c");
Node d = new Node("d");
Node e = new Node("e");
p.depends = Arrays.asList(d, a);
a.depends = Arrays.asList(e, b);
b.depends = Arrays.asList(d, c);
c.depends = Arrays.asList(d);
d.depends = Arrays.asList(e);
p.computeDominators();
assertEquals(p, a.dominator);
assertEquals(a, b.dominator);
assertEquals(b, c.dominator);
assertEquals(p, d.dominator);
assertEquals(p, e.dominator);
}
}