ahat: Improve field diffing.
* Factor field diffing code out of Diff into its own class.
* Switch to a new interface for diffing fields that does not rely on
being passed mutable lists.
* Reimplement field diff to work better when fields have been added,
deleted, or reordered.
Bug: 62408050
Test: m ahat-test, with new tests for field diff added.
Change-Id: I56c0414f8f4c11809895d809494d752201d33563
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index d6f1faa..cc55b7a 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -21,13 +21,15 @@
import com.android.ahat.heapdump.AhatClassObj;
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
-import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.DiffFields;
+import com.android.ahat.heapdump.DiffedFieldValue;
import com.android.ahat.heapdump.FieldValue;
import com.android.ahat.heapdump.PathElement;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
import java.io.IOException;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -108,13 +110,9 @@
private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
doc.section("Fields");
AhatInstance base = inst.getBaseline();
- List<FieldValue> fields = inst.getInstanceFields();
- if (!base.isPlaceHolder()) {
- Diff.fields(fields, base.asClassInstance().getInstanceFields());
- }
- SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
- printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
- selector.render(doc);
+ printFields(doc, query, INSTANCE_FIELDS_ID, !base.isPlaceHolder(),
+ inst.asClassInstance().getInstanceFields(),
+ base.isPlaceHolder() ? null : base.asClassInstance().getInstanceFields());
}
private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
@@ -145,36 +143,61 @@
selector.render(doc);
}
- private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
+ /**
+ * Helper function for printing static or instance fields.
+ * @param id - id to use for the field subset selector.
+ * @param diff - whether a diff should be shown for the fields.
+ * @param current - the fields to show.
+ * @param baseline - the baseline fields to diff against if diff is true,
+ * ignored otherwise.
+ */
+ private static void printFields(Doc doc, Query query, String id, boolean diff,
+ List<FieldValue> current, List<FieldValue> baseline) {
+
+ if (!diff) {
+ // To avoid having to special case when diff is disabled, always diff
+ // the fields, but diff against an empty list.
+ baseline = Collections.emptyList();
+ }
+
+ List<DiffedFieldValue> fields = DiffFields.diff(current, baseline);
+ SubsetSelector<DiffedFieldValue> selector = new SubsetSelector(query, id, fields);
+
doc.table(
new Column("Type"),
new Column("Name"),
new Column("Value"),
new Column("Δ", Column.Align.LEFT, diff));
- for (FieldValue field : fields) {
- Value current = field.getValue();
- DocString value;
- if (field.isPlaceHolder()) {
- value = DocString.removed("del");
- } else {
- value = Summarizer.summarize(current);
- }
+ for (DiffedFieldValue field : selector.selected()) {
+ Value previous = Value.getBaseline(field.baseline);
+ DocString was = DocString.text("was ");
+ was.append(Summarizer.summarize(previous));
+ switch (field.status) {
+ case ADDED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ Summarizer.summarize(field.current),
+ DocString.added("new"));
+ break;
- DocString delta = new DocString();
- FieldValue basefield = field.getBaseline();
- if (basefield.isPlaceHolder()) {
- delta.append(DocString.added("new"));
- } else {
- Value previous = Value.getBaseline(basefield.getValue());
- if (!Objects.equals(current, previous)) {
- delta.append("was ");
- delta.append(Summarizer.summarize(previous));
- }
+ case MATCHED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ Summarizer.summarize(field.current),
+ Objects.equals(field.current, previous) ? new DocString() : was);
+ break;
+
+ case DELETED:
+ doc.row(DocString.text(field.type),
+ DocString.text(field.name),
+ DocString.removed("del"),
+ was);
+ break;
}
- doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
}
doc.end();
+ selector.render(doc);
}
private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
@@ -188,13 +211,9 @@
doc.section("Static Fields");
AhatInstance base = clsobj.getBaseline();
- List<FieldValue> fields = clsobj.getStaticFieldValues();
- if (!base.isPlaceHolder()) {
- Diff.fields(fields, base.asClassObj().getStaticFieldValues());
- }
- SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
- printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
- selector.render(doc);
+ printFields(doc, query, STATIC_FIELDS_ID, !base.isPlaceHolder(),
+ clsobj.getStaticFieldValues(),
+ base.isPlaceHolder() ? null : base.asClassObj().getStaticFieldValues());
}
private static void printReferences(Doc doc, Query query, AhatInstance inst) {
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index c10d604..158de52 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -54,8 +54,8 @@
@Override public Value getField(String fieldName) {
for (FieldValue field : mFieldValues) {
- if (fieldName.equals(field.getName())) {
- return field.getValue();
+ if (fieldName.equals(field.name)) {
+ return field.value;
}
}
return null;
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
index 943e6e6..489f709 100644
--- a/tools/ahat/src/heapdump/Diff.java
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -336,48 +336,4 @@
placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
}
}
-
- /**
- * Diff two lists of field values.
- * PlaceHolder objects are added to the given lists as needed to ensure
- * every FieldValue in A ends up with a corresponding FieldValue in B.
- */
- public static void fields(List<FieldValue> a, List<FieldValue> b) {
- // Fields with the same name and type are considered matching fields.
- // For simplicity, we assume the matching fields are in the same order in
- // both A and B, though some fields may be added or removed in either
- // list. If our assumption is wrong, in the worst case the quality of the
- // field diff is poor.
-
- for (int i = 0; i < a.size(); i++) {
- FieldValue afield = a.get(i);
- afield.setBaseline(null);
-
- // Find the matching field in B, if any.
- for (int j = i; j < b.size(); j++) {
- FieldValue bfield = b.get(j);
- if (afield.getName().equals(bfield.getName())
- && afield.getType().equals(bfield.getType())) {
- // We found the matching field in B.
- // Assume fields i, ..., j-1 in B have no match in A.
- for ( ; i < j; i++) {
- a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
- }
-
- afield.setBaseline(bfield);
- bfield.setBaseline(afield);
- break;
- }
- }
-
- if (afield.getBaseline() == null) {
- b.add(i, FieldValue.newPlaceHolderFieldValue(afield));
- }
- }
-
- // All remaining fields in B are unmatched by any in A.
- for (int i = a.size(); i < b.size(); i++) {
- a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
- }
- }
}
diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/heapdump/DiffFields.java
new file mode 100644
index 0000000..dd73456
--- /dev/null
+++ b/tools/ahat/src/heapdump/DiffFields.java
@@ -0,0 +1,85 @@
+/*
+ * 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.heapdump;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class contains a routine for diffing two collections of static or
+ * instance fields.
+ */
+public class DiffFields {
+ /**
+ * Return the result of diffing two collections of field values.
+ * The input collections 'current' and 'baseline' are not modified by this function.
+ */
+ public static List<DiffedFieldValue> diff(Collection<FieldValue> current,
+ Collection<FieldValue> baseline) {
+ List<FieldValue> currentSorted = new ArrayList<FieldValue>(current);
+ Collections.sort(currentSorted, FOR_DIFF);
+
+ List<FieldValue> baselineSorted = new ArrayList<FieldValue>(baseline);
+ Collections.sort(baselineSorted, FOR_DIFF);
+
+ // Merge the two lists to form the diffed list of fields.
+ List<DiffedFieldValue> diffed = new ArrayList<DiffedFieldValue>();
+ int currentPos = 0;
+ int baselinePos = 0;
+
+ while (currentPos < currentSorted.size() && baselinePos < baselineSorted.size()) {
+ FieldValue currentField = currentSorted.get(currentPos);
+ FieldValue baselineField = baselineSorted.get(baselinePos);
+ int compare = FOR_DIFF.compare(currentField, baselineField);
+ if (compare < 0) {
+ diffed.add(DiffedFieldValue.added(currentField));
+ currentPos++;
+ } else if (compare == 0) {
+ diffed.add(DiffedFieldValue.matched(currentField, baselineField));
+ currentPos++;
+ baselinePos++;
+ } else {
+ diffed.add(DiffedFieldValue.deleted(baselineField));
+ baselinePos++;
+ }
+ }
+
+ while (currentPos < currentSorted.size()) {
+ FieldValue currentField = currentSorted.get(currentPos);
+ diffed.add(DiffedFieldValue.added(currentField));
+ currentPos++;
+ }
+
+ while (baselinePos < baselineSorted.size()) {
+ FieldValue baselineField = baselineSorted.get(baselinePos);
+ diffed.add(DiffedFieldValue.deleted(baselineField));
+ baselinePos++;
+ }
+ return diffed;
+ }
+
+ /**
+ * Comparator used for sorting fields for the purposes of diff.
+ * Fields with the same name and type are considered comparable, so we sort
+ * by field name and type.
+ */
+ private static final Comparator<FieldValue> FOR_DIFF
+ = new Sort.WithPriority(Sort.FIELD_VALUE_BY_NAME, Sort.FIELD_VALUE_BY_TYPE);
+}
diff --git a/tools/ahat/src/heapdump/DiffedFieldValue.java b/tools/ahat/src/heapdump/DiffedFieldValue.java
new file mode 100644
index 0000000..e2dcf3e
--- /dev/null
+++ b/tools/ahat/src/heapdump/DiffedFieldValue.java
@@ -0,0 +1,101 @@
+/*
+ * 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.heapdump;
+
+import java.util.Objects;
+
+/** DiffedFieldValue is used by the DiffedField class to return the result of
+ * diffing two collections of fields.
+ */
+public class DiffedFieldValue {
+ public final String name;
+ public final String type;
+ public final Value current;
+ public final Value baseline;
+
+ public final Status status;
+
+ public static enum Status {
+ ADDED, // The current field has no matching baseline value.
+ MATCHED, // The current field has a matching baseline value.
+ DELETED // The baseline field has no matching current value.
+ };
+
+ /**
+ * Return a DiffedFieldValue where there is both a current and baseline.
+ */
+ public static DiffedFieldValue matched(FieldValue current, FieldValue baseline) {
+ return new DiffedFieldValue(current.name,
+ current.type,
+ current.value,
+ baseline.value,
+ Status.MATCHED);
+ }
+
+ /**
+ * Return a DiffedFieldValue where there is no baseline.
+ */
+ public static DiffedFieldValue added(FieldValue current) {
+ return new DiffedFieldValue(current.name, current.type, current.value, null, Status.ADDED);
+ }
+
+ /**
+ * Return a DiffedFieldValue where there is no current.
+ */
+ public static DiffedFieldValue deleted(FieldValue baseline) {
+ return new DiffedFieldValue(baseline.name, baseline.type, null, baseline.value, Status.DELETED);
+ }
+
+ private DiffedFieldValue(String name, String type, Value current, Value baseline, Status status) {
+ this.name = name;
+ this.type = type;
+ this.current = current;
+ this.baseline = baseline;
+ this.status = status;
+ }
+
+ @Override
+ public boolean equals(Object otherObject) {
+ if (otherObject instanceof DiffedFieldValue) {
+ DiffedFieldValue other = (DiffedFieldValue)otherObject;
+ return name.equals(other.name)
+ && type.equals(other.type)
+ && Objects.equals(current, other.current)
+ && Objects.equals(baseline, other.baseline)
+ && Objects.equals(status, other.status);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ switch (status) {
+ case ADDED:
+ return "(" + name + " " + type + " +" + current + ")";
+
+ case MATCHED:
+ return "(" + name + " " + type + " " + current + " " + baseline + ")";
+
+ case DELETED:
+ return "(" + name + " " + type + " -" + baseline + ")";
+
+ default:
+ // There are no other members.
+ throw new AssertionError("unsupported enum member");
+ }
+ }
+}
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
index 3f65cd3..6d72595 100644
--- a/tools/ahat/src/heapdump/FieldValue.java
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -16,68 +16,14 @@
package com.android.ahat.heapdump;
-public class FieldValue implements Diffable<FieldValue> {
- private final String mName;
- private final String mType;
- private final Value mValue;
- private FieldValue mBaseline;
- private final boolean mIsPlaceHolder;
+public class FieldValue {
+ public final String name;
+ public final String type;
+ public final Value value;
public FieldValue(String name, String type, Value value) {
- mName = name;
- mType = type;
- mValue = value;
- mBaseline = this;
- mIsPlaceHolder = false;
- }
-
- /**
- * Construct a place holder FieldValue
- */
- private FieldValue(FieldValue baseline) {
- mName = baseline.mName;
- mType = baseline.mType;
- mValue = Value.getBaseline(baseline.mValue);
- mBaseline = baseline;
- mIsPlaceHolder = true;
- }
-
- static FieldValue newPlaceHolderFieldValue(FieldValue baseline) {
- FieldValue field = new FieldValue(baseline);
- baseline.setBaseline(field);
- return field;
- }
-
- /**
- * Returns the name of the field.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a description of the type of the field.
- */
- public String getType() {
- return mType;
- }
-
- /**
- * Returns the value of this field.
- */
- public Value getValue() {
- return mValue;
- }
-
- public void setBaseline(FieldValue baseline) {
- mBaseline = baseline;
- }
-
- @Override public FieldValue getBaseline() {
- return mBaseline;
- }
-
- @Override public boolean isPlaceHolder() {
- return mIsPlaceHolder;
+ this.name = name;
+ this.type = type;
+ this.value = value;
}
}
diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/heapdump/Sort.java
index 0745803..efe0d6b 100644
--- a/tools/ahat/src/heapdump/Sort.java
+++ b/tools/ahat/src/heapdump/Sort.java
@@ -200,5 +200,27 @@
return aName.compareTo(bName);
}
};
+
+ /**
+ * Compare FieldValue by field name.
+ */
+ public static final Comparator<FieldValue> FIELD_VALUE_BY_NAME
+ = new Comparator<FieldValue>() {
+ @Override
+ public int compare(FieldValue a, FieldValue b) {
+ return a.name.compareTo(b.name);
+ }
+ };
+
+ /**
+ * Compare FieldValue by type name.
+ */
+ public static final Comparator<FieldValue> FIELD_VALUE_BY_TYPE
+ = new Comparator<FieldValue>() {
+ @Override
+ public int compare(FieldValue a, FieldValue b) {
+ return a.type.compareTo(b.type);
+ }
+ };
}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
index 6b2d38f..c1f3022 100644
--- a/tools/ahat/src/heapdump/Value.java
+++ b/tools/ahat/src/heapdump/Value.java
@@ -28,7 +28,7 @@
* The Object must either be a boxed Java primitive type or a subclass of
* AhatInstance. The object must not be null.
*/
- Value(Object object) {
+ public Value(Object object) {
// TODO: Check that the Object is either an AhatSnapshot or boxed Java
// primitive type?
assert object != null;
diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/test/DiffFieldsTest.java
new file mode 100644
index 0000000..6abdd47
--- /dev/null
+++ b/tools/ahat/test/DiffFieldsTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 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.heapdump.DiffFields;
+import com.android.ahat.heapdump.DiffedFieldValue;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Value;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class DiffFieldsTest {
+ @Test
+ public void normalMatchedDiffedFieldValues() {
+ FieldValue normal1 = new FieldValue("name", "type", new Value(1));
+ FieldValue normal2 = new FieldValue("name", "type", new Value(2));
+
+ DiffedFieldValue x = DiffedFieldValue.matched(normal1, normal2);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertEquals(new Value(2), x.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
+ }
+
+ @Test
+ public void nulledMatchedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.matched(normal, nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertNull(x.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
+
+ DiffedFieldValue y = DiffedFieldValue.matched(nulled, normal);
+ assertEquals("name", y.name);
+ assertEquals("type", y.type);
+ assertNull(y.current);
+ assertEquals(new Value(1), y.baseline);
+ assertEquals(DiffedFieldValue.Status.MATCHED, y.status);
+ }
+
+ @Test
+ public void normalAddedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+
+ DiffedFieldValue x = DiffedFieldValue.added(normal);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.current);
+ assertEquals(DiffedFieldValue.Status.ADDED, x.status);
+ }
+
+ @Test
+ public void nulledAddedDiffedFieldValues() {
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.added(nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertNull(x.current);
+ assertEquals(DiffedFieldValue.Status.ADDED, x.status);
+ }
+
+ @Test
+ public void normalDeletedDiffedFieldValues() {
+ FieldValue normal = new FieldValue("name", "type", new Value(1));
+
+ DiffedFieldValue x = DiffedFieldValue.deleted(normal);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertEquals(new Value(1), x.baseline);
+ assertEquals(DiffedFieldValue.Status.DELETED, x.status);
+ }
+
+ @Test
+ public void nulledDeletedDiffedFieldValues() {
+ FieldValue nulled = new FieldValue("name", "type", null);
+
+ DiffedFieldValue x = DiffedFieldValue.deleted(nulled);
+ assertEquals("name", x.name);
+ assertEquals("type", x.type);
+ assertNull(x.baseline);
+ assertEquals(DiffedFieldValue.Status.DELETED, x.status);
+ }
+
+ @Test
+ public void basicDiff() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n6", "t6", null));
+ b.add(new FieldValue("n7", "t7", null));
+
+ // Note: The expected result makes assumptions about the implementation of
+ // field diff to match the order of the returned fields. If the
+ // implementation changes, this test may need to be generalized to accept
+ // the new implementation.
+ List<DiffedFieldValue> expected = new ArrayList<DiffedFieldValue>();
+ expected.add(DiffedFieldValue.matched(a.get(0), b.get(0)));
+ expected.add(DiffedFieldValue.deleted(b.get(1)));
+ expected.add(DiffedFieldValue.matched(a.get(1), b.get(2)));
+ expected.add(DiffedFieldValue.matched(a.get(2), b.get(3)));
+ expected.add(DiffedFieldValue.added(a.get(3)));
+ expected.add(DiffedFieldValue.matched(a.get(4), b.get(4)));
+ expected.add(DiffedFieldValue.matched(a.get(5), b.get(5)));
+ expected.add(DiffedFieldValue.deleted(b.get(6)));
+
+ List<DiffedFieldValue> diffed = DiffFields.diff(a, b);
+ assertEquals(expected, diffed);
+ }
+
+ @Test
+ public void reorderedDiff() {
+ List<FieldValue> a = new ArrayList<FieldValue>();
+ a.add(new FieldValue("n0", "t0", null));
+ a.add(new FieldValue("n1", "t1", null));
+ a.add(new FieldValue("n2", "t2", null));
+ a.add(new FieldValue("n3", "t3", null));
+ a.add(new FieldValue("n4", "t4", null));
+ a.add(new FieldValue("n5", "t5", null));
+ a.add(new FieldValue("n6", "t6", null));
+
+ List<FieldValue> b = new ArrayList<FieldValue>();
+ b.add(new FieldValue("n4", "t4", null));
+ b.add(new FieldValue("n1", "t1", null));
+ b.add(new FieldValue("n3", "t3", null));
+ b.add(new FieldValue("n0", "t0", null));
+ b.add(new FieldValue("n5", "t5", null));
+ b.add(new FieldValue("n2", "t2", null));
+ b.add(new FieldValue("n6", "t6", null));
+
+ // Note: The expected result makes assumptions about the implementation of
+ // field diff to match the order of the returned fields. If the
+ // implementation changes, this test may need to be generalized to accept
+ // the new implementation.
+ List<DiffedFieldValue> expected = new ArrayList<DiffedFieldValue>();
+ expected.add(DiffedFieldValue.matched(a.get(0), b.get(3)));
+ expected.add(DiffedFieldValue.matched(a.get(1), b.get(1)));
+ expected.add(DiffedFieldValue.matched(a.get(2), b.get(5)));
+ expected.add(DiffedFieldValue.matched(a.get(3), b.get(2)));
+ expected.add(DiffedFieldValue.matched(a.get(4), b.get(0)));
+ expected.add(DiffedFieldValue.matched(a.get(5), b.get(4)));
+ expected.add(DiffedFieldValue.matched(a.get(6), b.get(6)));
+
+ List<DiffedFieldValue> diffed = DiffFields.diff(a, b);
+ assertEquals(expected, diffed);
+ }
+}
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
index 52b6b7b..d0349fd 100644
--- a/tools/ahat/test/DiffTest.java
+++ b/tools/ahat/test/DiffTest.java
@@ -20,7 +20,6 @@
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diff;
-import com.android.ahat.heapdump.FieldValue;
import com.android.tools.perflib.heap.hprof.HprofClassDump;
import com.android.tools.perflib.heap.hprof.HprofConstant;
import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
@@ -129,35 +128,4 @@
// Diffing should not crash.
Diff.snapshots(snapshot, snapshot);
}
-
- @Test
- public void diffFields() {
- List<FieldValue> a = new ArrayList<FieldValue>();
- a.add(new FieldValue("n0", "t0", null));
- a.add(new FieldValue("n2", "t2", null));
- a.add(new FieldValue("n3", "t3", null));
- a.add(new FieldValue("n4", "t4", null));
- a.add(new FieldValue("n5", "t5", null));
- a.add(new FieldValue("n6", "t6", null));
-
- List<FieldValue> b = new ArrayList<FieldValue>();
- b.add(new FieldValue("n0", "t0", null));
- b.add(new FieldValue("n1", "t1", null));
- b.add(new FieldValue("n2", "t2", null));
- b.add(new FieldValue("n3", "t3", null));
- b.add(new FieldValue("n5", "t5", null));
- b.add(new FieldValue("n6", "t6", null));
- b.add(new FieldValue("n7", "t7", null));
-
- Diff.fields(a, b);
- assertEquals(8, a.size());
- assertEquals(8, b.size());
- for (int i = 0; i < 8; i++) {
- assertEquals(a.get(i), b.get(i).getBaseline());
- assertEquals(b.get(i), a.get(i).getBaseline());
- }
- assertTrue(a.get(1).isPlaceHolder());
- assertTrue(a.get(7).isPlaceHolder());
- assertTrue(b.get(4).isPlaceHolder());
- }
}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index ceb7346..3dce2dc 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -118,9 +118,9 @@
private Value getDumpedValue(String name, AhatSnapshot snapshot) {
AhatClassObj main = snapshot.findClass("Main");
AhatInstance stuff = null;
- for (FieldValue fields : main.getStaticFieldValues()) {
- if ("stuff".equals(fields.getName())) {
- stuff = fields.getValue().asAhatInstance();
+ for (FieldValue field : main.getStaticFieldValues()) {
+ if ("stuff".equals(field.name)) {
+ stuff = field.value.asAhatInstance();
}
}
return stuff.getField(name);
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index c7e9b18..a95788e 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,6 +22,7 @@
public static void main(String[] args) {
if (args.length == 0) {
args = new String[]{
+ "com.android.ahat.DiffFieldsTest",
"com.android.ahat.DiffTest",
"com.android.ahat.InstanceTest",
"com.android.ahat.NativeAllocationTest",