Skip to content

Commit 22cd50f

Browse files
committed
test: Use PBT for testing SCCs
1 parent 62b46a8 commit 22cd50f

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

.scalafmt.conf

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ runner.dialect = scala3
44
assumeStandardLibraryStripMargin = true
55

66
newlines.inInterpolation = "avoid"
7+
8+
docstrings.oneline = "fold"
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package fred
2+
3+
import scala.collection.mutable
4+
5+
import org.scalacheck.Gen
6+
7+
object GenerateTypes {
8+
case class GeneratedType(refs: List[Int])
9+
10+
/** Generate a bunch of types. Each type is its own strongly-connected
11+
* component. The returned list is sorted backwards (types that come later in
12+
* the list can have references to types that come earlier in the list).
13+
*
14+
* Types can have references to themselves (so we can test cycles). To allow
15+
* this to work without problems, self-references must go through an Option
16+
* type generated for that type.
17+
*
18+
* All references are mutable, for testing cycles.
19+
*/
20+
def genTypes(): Gen[List[GeneratedType]] = {
21+
Gen.choose(1, 40).flatMap { numSccs =>
22+
Gen.sequence(0.until(numSccs).map { i =>
23+
Gen.listOf(Gen.chooseNum(0, i)).map(GeneratedType(_))
24+
})
25+
}
26+
}
27+
28+
def generateAst(typeAuxes: List[GeneratedType]): ParsedFile = {
29+
def nameFor(ind: Int) = s"T$ind"
30+
31+
/** Name for the optional type corresponding to the type at index `ind` */
32+
def nameForOpt(ind: Int) = s"OptT$ind"
33+
34+
val types = typeAuxes.zipWithIndex.map { case (GeneratedType(refs), i) =>
35+
val typeName = Spanned(nameFor(i), Span.synthetic)
36+
TypeDef(
37+
typeName,
38+
List(
39+
EnumCase(
40+
typeName,
41+
refs.zipWithIndex.map { (typeInd, fieldInd) =>
42+
val fieldType =
43+
if (i == typeInd) nameForOpt(typeInd) else nameFor(typeInd)
44+
FieldDef(
45+
true,
46+
Spanned(s"f$fieldInd", Span.synthetic),
47+
TypeRef(fieldType, Span.synthetic),
48+
Span.synthetic
49+
)
50+
},
51+
Span.synthetic
52+
)
53+
),
54+
Span.synthetic
55+
)
56+
}
57+
// Generate optional types for self-referential types
58+
val optTypes = typeAuxes.zipWithIndex
59+
.filter { case (GeneratedType(refs), i) =>
60+
refs.contains(i)
61+
}
62+
.map { (_, i) =>
63+
val name = Spanned(nameForOpt(i), Span.synthetic)
64+
TypeDef(
65+
name,
66+
List(
67+
EnumCase(
68+
name,
69+
List(
70+
FieldDef(
71+
false,
72+
Spanned("value", Span.synthetic),
73+
TypeRef(nameFor(i), Span.synthetic),
74+
Span.synthetic
75+
)
76+
),
77+
Span.synthetic
78+
)
79+
),
80+
Span.synthetic
81+
)
82+
}
83+
ParsedFile(types ++ optTypes, Nil)
84+
}
85+
}

src/test/scala/fred/SCCTests.scala

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package fred
22

33
import org.scalatest.funsuite.AnyFunSuite
4+
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
45
import snapshot4s.scalatest.SnapshotAssertions
56
import snapshot4s.generated.snapshotConfig
67

7-
class SCCTests extends AnyFunSuite with SnapshotAssertions {
8+
class SCCTests
9+
extends AnyFunSuite,
10+
SnapshotAssertions,
11+
ScalaCheckPropertyChecks {
812

913
/** This is just a helper to avoid typing out calls to the TypeDef ctor */
1014
def createFile(graph: Map[String, Set[(Boolean, String)]]): ParsedFile = {
@@ -112,4 +116,28 @@ class SCCTests extends AnyFunSuite with SnapshotAssertions {
112116
val cycles = Cycles.fromFile(file)
113117
assert(cycles.badSCCs.isEmpty)
114118
}
119+
120+
test("Ensure strongly-connected components valid using Gen") {
121+
forAll(GenerateTypes.genTypes()) { typesAux =>
122+
val file = GenerateTypes.generateAst(typesAux)
123+
val cycles = Cycles.fromFile(file)
124+
125+
assert(cycles.sccs.size === typesAux.size)
126+
127+
val bindings = Bindings.fromFile(file)
128+
for {
129+
typ <- file.typeDefs
130+
field <- typ.cases.head.fields
131+
} do {
132+
bindings.getType(field.typ) match {
133+
case td: TypeDef =>
134+
assert(
135+
cycles.sccMap(typ) <= cycles.sccMap(td),
136+
typesAux.mkString("\n") + "---\n" + cycles.sccs.map(_.map(_.name))
137+
)
138+
case _ => {}
139+
}
140+
}
141+
}
142+
}
115143
}

0 commit comments

Comments
 (0)