Skip to content

Commit 1e31028

Browse files
committed
Add ScalaCheck specs to test suite
Adds 26 specifications in ScalaCheck, with 154 properties, of 18 are proofs by example, and the rest are 136 tests using randomly-generated values. ScalaCheck requires at least 100 generated tests to pass, by default. So that gives 13,618 new tests! The suite should be a real benefit for future maintenance endeavors, rewrites, documentation, or finishing a Scala-based parser to avoid depending on the the Xerces/SAX Java library. These integrate well with the existing suite of JUnit 4 tests. If you want to run both the ScalaCheck and JUnit tests from SBT, > test ... [info] Passed: Total 145, Failed 0, Errors 0, Passed 145 [success] Total time: 43 s, completed Sep 30, 2016 2:19:56 PM If you want to run a single spec, > testOnly scala.xml.XMLSpec > testOnly scala.xml.XMLSpec [info] + XML.write: OK, passed 100 tests. [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 3 s, completed Sep 30, 2016 2:20:28 PM If you want to run all the ScalaCheck specs, > testOnly *Spec ... [info] Passed: Total 26, Failed 0, Errors 0, Passed 26 [success] Total time: 30 s, completed Sep 30, 2016 2:22:58 PM If you want to run the specs under a namespace, > testOnly scala.xml.dtd.*Spec ... [info] + dtd.DTD.toString: OK, passed 100 tests. [info] + dtd.DocType.new(name): OK, passed 100 tests. [info] + dtd.DocType.new(name, extId, intSubset): OK, passed 100 tests. [info] + dtd.DocType.new(name, extId, emptyInt): OK, passed 100 tests. [info] + dtd.DocType.toString: OK, passed 100 tests. [info] Passed: Total 4, Failed 0, Errors 0, Passed 4 [success] Total time: 6 s, completed Sep 30, 2016 3:14:05 PM If you want to generate 1000 tests instead of 100, > testOnly *Spec -- -s 1000 ... [info] Passed: Total 26, Failed 0, Errors 0, Passed 26 [success] Total time: 217 s, completed Sep 30, 2016 2:27:31 PM If you want to generate 10 tests, instead of 100, > testOnly *Spec -- -s 10 ... [info] Passed: Total 26, Failed 0, Errors 0, Passed 26 [success] Total time: 10 s, completed Sep 30, 2016 2:23:49 PM If you want to generate full back traces on errors and display the elapsed time for each property, > testOnly *Spec -- -verbosity 2 ... [info] + parsing.XhtmlParser.initialize: OK, passed 100 tests. [info] Elapsed time: 0.046 sec [info] + parsing.XhtmlParser.prolog: OK, passed 100 tests. [info] Elapsed time: 0.022 sec [info] + parsing.XhtmlParser.document: OK, passed 100 tests. [info] Elapsed time: 0.015 sec [info] Passed: Total 26, Failed 0, Errors 0, Passed 26 [success] Total time: 30 s, completed Sep 30, 2016 2:29:24 PM The default verbosity is 0, > testOnly *Spec -- -verbosity 0 ... [info] + parsing.XhtmlParser.initialize: OK, passed 100 tests. [info] + parsing.XhtmlParser.prolog: OK, passed 100 tests. [info] + parsing.XhtmlParser.document: OK, passed 100 tests. [info] Passed: Total 26, Failed 0, Errors 0, Passed 26 [success] Total time: 26 s, completed Sep 30, 2016 2:30:26 PM To run only the failed tests, > testQuick [info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] No tests to run for test:testQuick [success] Total time: 1 s, completed Sep 30, 2016 2:32:50 PM Currently, these tests in ScalaCheck will cover only 25% of the source code, and 20% of branches. Not surprisingly, there are some defects in scala-xml for certain types and classes. I am forced to comment out and disable those tests and generators so that the suite passes. Those issues should be taken up separately and in subsequent pull requests. When those tests and generators are re-enabled for those types, the code coverage would most likely increase. There are no shrink heuristics defined written to help ScalaCheck identify the minimum value to falsify a property of an XML string or data structure. This will be a nice to have, but was too advanced for me to take on at this time. Right now, the generators equally weight the Node types among those that are allowed. It would probably be worthwhile to have the frequencies be more realistic. For example, have a a greater emphasis on Elem and EntityRef elements over Comment, ProcInstr, nulls, empty strings and empty lists. The frequencies should be closer in line with what a typical XML file would be, but also more broadly cover the code and sooner using fewer generated values. Similarly, there is not an appropriate sizing for lists. Presently, ScalaCheck randomly selects a number, and then the generators I wrote will try to approach termination by recursively halving and square-rooting at each level of descent.
1 parent 1c40e3d commit 1e31028

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2958
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryElem {
7+
8+
def genElem(sz: Int): Gen[Elem]
9+
10+
implicit val arbElem: Arbitrary[Elem]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryGroup {
7+
8+
def genGroup(sz: Int): Gen[Group]
9+
10+
implicit val arbGroup: Arbitrary[Group]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryMetaData {
7+
8+
val genMetaData: Gen[MetaData]
9+
10+
implicit val arbMetaData: Arbitrary[MetaData]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryNode {
7+
8+
def genNode(sz: Int): Gen[Node]
9+
10+
implicit val arbNode: Arbitrary[Node]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryNodeBuffer {
7+
8+
val genNodeBuffer: Gen[NodeBuffer]
9+
10+
implicit val arbNodeBuffer: Arbitrary[NodeBuffer]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait ArbitraryTextBuffer {
7+
8+
val genTextBuffer: Gen[TextBuffer]
9+
10+
implicit val arbTextBuffer: Arbitrary[TextBuffer]
11+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait AtomGen extends PCDataGen
7+
with TextGen
8+
with UnparsedGen {
9+
10+
val genAtom: Gen[Atom[String]] =
11+
Gen.oneOf(genPCData, genText, genUnparsed)
12+
13+
implicit val arbAtom = Arbitrary {
14+
genAtom
15+
}
16+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Prop
4+
import org.scalacheck.{ Properties => PropertiesFor }
5+
import org.scalacheck.Prop.AnyOperators
6+
7+
object AtomSpec extends PropertiesFor("Atom")
8+
with AtomGen {
9+
10+
property("new(null).throws[Exception]") = {
11+
Prop.throws(classOf[IllegalArgumentException]) {
12+
new Atom(null)
13+
}
14+
}
15+
16+
property("data") = {
17+
Prop.forAll { d: Atom[String] =>
18+
d.data ne null
19+
}
20+
}
21+
22+
property("text") = {
23+
Prop.forAll { a: Atom[String] =>
24+
a.text ?= s"${a.data}"
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait AttributeGen extends TextGen
7+
with ArbitraryMetaData
8+
with XmlNameGen {
9+
10+
def genAttribute: Gen[Attribute] =
11+
Gen.oneOf(
12+
Arbitrary.arbitrary[PrefixedAttribute],
13+
Arbitrary.arbitrary[UnprefixedAttribute]
14+
)
15+
16+
val genPrefixedAttribute: Gen[PrefixedAttribute] = for {
17+
prefix <- genXmlName: Gen[String]
18+
key <- genXmlName: Gen[String]
19+
value <- Arbitrary.arbitrary[Text]
20+
next <- Gen.delay(genMetaData)
21+
} yield {
22+
new PrefixedAttribute(prefix, key, value, next)
23+
}
24+
25+
val genUnprefixedAttribute: Gen[UnprefixedAttribute] = for {
26+
key <- genXmlName: Gen[String]
27+
value <- Arbitrary.arbitrary[Text]
28+
next <- Gen.delay(genMetaData)
29+
} yield {
30+
new UnprefixedAttribute(key, value, next)
31+
}
32+
33+
implicit val arbAttribute: Arbitrary[Attribute] = Arbitrary {
34+
genAttribute
35+
}
36+
37+
implicit val arbPrefixedAttribute: Arbitrary[PrefixedAttribute] = Arbitrary {
38+
genPrefixedAttribute
39+
}
40+
41+
implicit val arbUnprefixedAttribute: Arbitrary[UnprefixedAttribute] = Arbitrary {
42+
genUnprefixedAttribute
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Prop
4+
import org.scalacheck.{ Properties => PropertiesFor }
5+
import org.scalacheck.Prop.AnyOperators
6+
import org.scalacheck.Prop.BooleanOperators
7+
8+
object AttributeSpec extends PropertiesFor("Attribute")
9+
with AttributeGen
10+
with NamespaceBindingGen
11+
with NodeGen
12+
with MetaDataGen {
13+
14+
property("canEqual(this)") = {
15+
Prop.forAll { a: Attribute =>
16+
a.canEqual(a)
17+
}
18+
}
19+
20+
property("apply") = {
21+
Prop.forAll { (a: Attribute, s: String) =>
22+
(!s.isEmpty && Utility.isNameStart(s(0))) ==>
23+
(a(s) != s)
24+
}
25+
}
26+
27+
property("apply(key)") = {
28+
Prop.forAll { a: Attribute =>
29+
Prop.iff[Attribute](a, {
30+
case p: PrefixedAttribute =>
31+
Prop.passed
32+
case u: UnprefixedAttribute if u.key ne null =>
33+
u(u.key) ?= u.value
34+
case _ => Prop.falsified
35+
})
36+
}
37+
}
38+
39+
property("apply(uri, namespace, key)") = {
40+
Prop.forAll { a: Attribute =>
41+
Prop.iff[Attribute](a, {
42+
case p: PrefixedAttribute =>
43+
val res = p(p.key, NamespaceBinding(p.pre, p.key, TopScope), p.key)
44+
res ?= p.value
45+
case u: UnprefixedAttribute =>
46+
Prop.passed
47+
case _ => Prop.falsified
48+
})
49+
}
50+
}
51+
52+
property("isPrefixed") = {
53+
Prop.forAll { a: Attribute =>
54+
Prop.iff[Attribute](a, {
55+
case p: PrefixedAttribute =>
56+
a.isPrefixed ?= true
57+
case u: UnprefixedAttribute =>
58+
a.isPrefixed ?= false
59+
case _ => Prop.falsified
60+
})
61+
}
62+
}
63+
64+
property("length") = {
65+
Prop.forAll { a: Attribute =>
66+
a.length >= 0
67+
}
68+
}
69+
70+
property("size") = {
71+
Prop.forAll { a: Attribute =>
72+
a.size >= 0
73+
}
74+
}
75+
76+
property("toString") = {
77+
Prop.forAll { a: Attribute =>
78+
val str = a.toString
79+
Prop.iff[Attribute](a, {
80+
case a: PrefixedAttribute if a.hasNext =>
81+
str ?= s""" ${a.pre}:${a.key}="${a.value}"${a.next}"""
82+
case a: UnprefixedAttribute if a.hasNext =>
83+
str ?= s""" ${a.key}="${a.value}"${a.next}"""
84+
case a: PrefixedAttribute =>
85+
str ?= s""" ${a.pre}:${a.key}="${a.value}""""
86+
case a: UnprefixedAttribute =>
87+
str ?= s""" ${a.key}="${a.value}""""
88+
case _ => Prop.falsified
89+
})
90+
}
91+
}
92+
93+
property("unapply") = {
94+
Prop.forAll { a: Attribute =>
95+
Attribute.unapply(a) ?= Some((a.key, a.value, a.next))
96+
}
97+
}
98+
99+
property("remove(key)") = {
100+
Prop.forAll { a: Attribute =>
101+
Prop.iff[Attribute](a, {
102+
case a: PrefixedAttribute =>
103+
a.remove(a.key) ?= a.copy(a.next.remove(a.key)) // FIXME: ???
104+
case a: UnprefixedAttribute =>
105+
a.remove(a.key) ?= a.next
106+
case _ => Prop.falsified
107+
})
108+
}
109+
}
110+
111+
property("remove") = {
112+
Prop.forAll { (a: Attribute, s: String) =>
113+
s != a.key ==>
114+
(a.remove(s) ?= a.copy(a.next.remove(s)))
115+
}
116+
}
117+
118+
property("getNamespace") = {
119+
Prop.forAll { (a: Attribute, n: Node) =>
120+
a.getNamespace(n) ?= n.getNamespace(a.pre)
121+
}
122+
}
123+
124+
property("wellformed") = {
125+
Prop.forAll { (a: Attribute, scope: NamespaceBinding) =>
126+
Prop.iff[Attribute](a, {
127+
case a: PrefixedAttribute =>
128+
val res = a.wellformed(scope)
129+
res ?= ((null eq a.next(scope.getURI(a.pre), scope, a.key)) && a.next.wellformed(scope))
130+
case a: UnprefixedAttribute =>
131+
val res = a.wellformed(scope)
132+
res ?= ((null eq a.next(null, scope, a.key)) && a.next.wellformed(scope))
133+
case _ => Prop.falsified
134+
})
135+
}
136+
}
137+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Arbitrary
4+
import org.scalacheck.Gen
5+
6+
trait CommentGen extends Utf8StringGen {
7+
8+
val genComment: Gen[Comment] = for {
9+
s <- genUtf8String: Gen[String] if !s.contains("--") && !s.endsWith("-")
10+
} yield {
11+
Comment(s)
12+
}
13+
14+
implicit val arbComment = Arbitrary {
15+
genComment
16+
}
17+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package scala.xml
2+
3+
import org.scalacheck.Prop
4+
import org.scalacheck.{ Properties => PropertiesFor }
5+
import org.scalacheck.Prop.AnyOperators
6+
7+
object CommentSpec extends PropertiesFor("Comment")
8+
with CommentGen {
9+
10+
property("new(--).throws[Exception]") = {
11+
Prop.throws(classOf[IllegalArgumentException]) {
12+
new Comment("--")
13+
}
14+
}
15+
16+
property("child") = {
17+
Prop.forAll { n: Comment =>
18+
n.child ?= Nil
19+
}
20+
}
21+
22+
property("text") = {
23+
Prop.forAll { n: Comment =>
24+
n.text ?= ""
25+
}
26+
}
27+
28+
property("toString") = {
29+
Prop.forAll { n: Comment =>
30+
val str = n.toString
31+
Prop.atLeastOne(
32+
str ?= "<!---->",
33+
str.startsWith("<!--") && str.endsWith("-->")
34+
)
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)