Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement basic cycle detection! (#4)
* Add basic cycle detection uses an ArrayDeque as a stack. currently this is not thread safe and will need to be modified when a concurrent strategy is implemented Adds and removes the `System.identityHashCode()` from a stack. when the hash code is detected in the list of currently processing nodes, a cycle has been detected. Add the hash code to a list of identifiers (in case we encounter many cycles in a single node tree, and then immediately return. This is faster than object comparison and has relatively low memory overhead, but is most likely not optimal * Add cycle detection tests * Add readme bit for cycle detection add another test for small objects
- Loading branch information
Showing
5 changed files
with
244 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/test/kotlin/com/tylerthrailkill/helpers/prettyprint/CycleDetectionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package com.tylerthrailkill.helpers.prettyprint | ||
|
||
import org.spekframework.spek2.Spek | ||
import org.spekframework.spek2.style.specification.describe | ||
|
||
object CycleDetectionTest : Spek({ | ||
setup() | ||
|
||
describe("pretty printing") { | ||
context("plain objects with cycles") { | ||
it("should detect a cycle with plain Unit") { | ||
val unit = Unit | ||
val identity = System.identityHashCode(unit) | ||
prettyPrint(unit) mapsTo """ | ||
Unit( | ||
INSTANCE = cyclic reference detected for $identity | ||
)[${'$'}id=$identity] | ||
""" | ||
} | ||
it("should detect a cycle with two small objects") { | ||
val sco1 = SmallCyclicalObject1() | ||
val sco2 = SmallCyclicalObject2(sco1) | ||
sco1.c = sco2 | ||
val identity = System.identityHashCode(sco1) | ||
prettyPrint(sco1) mapsTo """ | ||
SmallCyclicalObject1( | ||
c = SmallCyclicalObject2( | ||
c = cyclic reference detected for $identity | ||
) | ||
)[${'$'}id=$identity] | ||
""" | ||
} | ||
it("should detect no cycle when an element is repeated several times in the same objects fields") { | ||
val smallObject = SmallObject("a string in small object", 777) | ||
val nestedLargeObjectNull = NestedLargeObject( | ||
NestedSmallObject(smallObject), | ||
smallObject, | ||
"test string, please don't break", | ||
null | ||
) | ||
prettyPrint(nestedLargeObjectNull) mapsTo """ | ||
NestedLargeObject( | ||
nestedSmallObject = NestedSmallObject( | ||
smallObject = SmallObject( | ||
field1 = a string in small object | ||
field2 = 777 | ||
) | ||
) | ||
smallObject = SmallObject( | ||
field1 = a string in small object | ||
field2 = 777 | ||
) | ||
testString = test string, please don't break | ||
bigObject = null | ||
) | ||
""" | ||
} | ||
} | ||
context("maps with cycles") { | ||
it("should detect a cycle between an object with a map with an object with a cycle") { | ||
val objectWithMap = ObjectWithMap( | ||
mutableMapOf(1 to null) | ||
) | ||
val objectContainingObjectWithMap = ObjectContainingObjectWithMap() | ||
objectContainingObjectWithMap.objectWithMap = objectWithMap | ||
objectWithMap.map[1] = objectContainingObjectWithMap | ||
val identity = System.identityHashCode(objectWithMap) | ||
prettyPrint(objectWithMap) mapsTo """ | ||
ObjectWithMap( | ||
map = { | ||
1 -> ObjectContainingObjectWithMap( | ||
objectWithMap = cyclic reference detected for $identity | ||
) | ||
} | ||
)[${'$'}id=$identity] | ||
""".trimIndent() | ||
} | ||
it("should detect a cycle of a map containing itself") { | ||
val outerMap: MutableMap<Int, Any?> = mutableMapOf(1 to null) | ||
val innerMap = mutableMapOf(1 to outerMap) | ||
outerMap[1] = innerMap | ||
val identity = System.identityHashCode(outerMap) | ||
prettyPrint(outerMap) mapsTo """ | ||
{ | ||
1 -> { | ||
1 -> cyclic reference detected for $identity | ||
} | ||
}[${'$'}id=$identity] | ||
""".trimIndent() | ||
} | ||
} | ||
context("lists with cycles") { | ||
it("should detect a cycle between an object with a list with an object with a cycle") { | ||
val objectWithList = ObjectWithList(mutableListOf()) | ||
val objectContainingObjectWithList = ObjectContainingObjectWithList() | ||
objectContainingObjectWithList.objectWithList = objectWithList | ||
objectWithList.list.add(objectContainingObjectWithList) | ||
val identity = System.identityHashCode(objectWithList) | ||
prettyPrint(objectWithList) mapsTo """ | ||
ObjectWithList( | ||
list = [ | ||
ObjectContainingObjectWithList( | ||
objectWithList = cyclic reference detected for $identity | ||
) | ||
] | ||
)[${'$'}id=$identity] | ||
""".trimIndent() | ||
} | ||
it("should detect a cycle of a list containing itself") { | ||
val outerList: MutableList<Any?> = mutableListOf() | ||
val innerList = mutableListOf(outerList) | ||
outerList.add(innerList) | ||
val identity = System.identityHashCode(outerList) | ||
prettyPrint(outerList) mapsTo """ | ||
[ | ||
[ | ||
cyclic reference detected for $identity | ||
] | ||
][${'$'}id=$identity] | ||
""".trimIndent() | ||
} | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,4 @@ | |
<root level="debug"> | ||
<appender-ref ref="FILE"/> | ||
</root> | ||
</configuration> | ||
</configuration> |