Skip to content

Commit 6fdd6b0

Browse files
committed
test: Add stupid PBT
1 parent 22cd50f commit 6fdd6b0

File tree

5 files changed

+160
-49
lines changed

5 files changed

+160
-49
lines changed

src/main/scala/fred/AST.scala

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package fred
22

3-
case class Span(start: Int, end: Int)
3+
case class Span(start: Int, end: Int) {
4+
override def toString =
5+
if (start == -1 && end == -1) "(:)"
6+
else s"($start:$end)"
7+
}
48

59
object Span {
6-
def synthetic = Span(-1, -1)
10+
11+
/** A dummy span to use for generated code */
12+
def synth = Span(-1, -1)
713
}
814

9-
case class Spanned[T](value: T, span: Span)
15+
case class Spanned[T](value: T, span: Span) {
16+
override def toString: String = value.toString
17+
}
1018

1119
case class ParsedFile(typeDefs: List[TypeDef], fns: List[FnDef])
1220

@@ -90,8 +98,12 @@ enum BinOp(val text: String) {
9098
case Seq extends BinOp(";")
9199
}
92100

93-
case class BinExpr(lhs: Expr, op: Spanned[BinOp], rhs: Expr, typ: Option[Type])
94-
extends Expr {
101+
case class BinExpr(
102+
lhs: Expr,
103+
op: Spanned[BinOp],
104+
rhs: Expr,
105+
typ: Option[Type] = None
106+
) extends Expr {
95107
override def span = Span(lhs.span.start, rhs.span.end)
96108
}
97109

@@ -105,8 +117,7 @@ case class IfExpr(cond: Expr, thenBody: Expr, elseBody: Expr, span: Span)
105117
override def typ = None
106118
}
107119

108-
/** An expression for setting a field on a variable
109-
*/
120+
/** An expression for setting a field on a variable */
110121
case class SetFieldExpr(
111122
lhsObj: Spanned[String],
112123
lhsField: Spanned[String],
@@ -116,8 +127,7 @@ case class SetFieldExpr(
116127
override def typ = None
117128
}
118129

119-
/** What a variable reference can resolve to
120-
*/
130+
/** What a variable reference can resolve to */
121131
enum VarDef {
122132
case Let(expr: LetExpr, typ: Type)
123133
case Param(param: fred.Param, typ: Type)

src/main/scala/fred/Type.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ enum BuiltinType extends Type {
1717
}
1818
}
1919

20-
override def span = Span.synthetic
20+
override def span = Span.synth
2121
}
2222

2323
case class TypeDef(

src/test/scala/fred/ExecTests.scala

+43-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
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

78
import scala.sys.process.*
89
import java.nio.file.Paths
10+
import java.nio.file.Files
11+
import java.nio.file.Path
12+
13+
class ExecTests
14+
extends AnyFunSuite,
15+
SnapshotAssertions,
16+
ScalaCheckPropertyChecks {
17+
def valgrindCheck(code: String, outFile: String)(expected: String): Unit = {
18+
valgrindCheck(Parser.parse(code), outFile, Some(expected), snapshot = true)
19+
}
920

10-
class ExecTests extends AnyFunSuite with SnapshotAssertions {
11-
def valgrindCheck(code: String, outFile: String)(expected: String) = {
12-
val parsedFile = Parser.parse(code)
21+
def valgrindCheck(
22+
parsedFile: ParsedFile,
23+
outFile: String,
24+
expected: Option[String],
25+
snapshot: Boolean
26+
): Unit = {
1327
given typer: Typer = Typer.resolveAllTypes(parsedFile)
1428
val generatedC = Translator.toC(parsedFile)
1529

16-
assertFileSnapshot(generatedC, s"exec/$outFile")
30+
val outPath =
31+
if (snapshot) {
32+
assertFileSnapshot(generatedC, s"exec/$outFile")
33+
s"src/test/resources/snapshot/exec/$outFile"
34+
} else {
35+
Files.write(Path.of(outFile), generatedC.getBytes())
36+
outFile
37+
}
1738

18-
s"gcc -g -I ${Compiler.includesFolder()} src/test/resources/snapshot/exec/$outFile".!!
39+
s"gcc -g -I ${Compiler.includesFolder()} $outPath".!!
1940

2041
val stderrBuf = StringBuilder()
2142
val stdout =
@@ -29,7 +50,11 @@ class ExecTests extends AnyFunSuite with SnapshotAssertions {
2950
throw RuntimeException(stderrBuf.toString, e)
3051
}
3152
val valgrindOut = stderrBuf.toString
32-
assert(stdout.trim() === expected.trim(), valgrindOut)
53+
expected match {
54+
case Some(expected) =>
55+
assert(stdout.trim() === expected.trim(), valgrindOut)
56+
case None =>
57+
}
3358
assert(valgrindOut.contains("ERROR SUMMARY: 0 errors"), valgrindOut)
3459
}
3560

@@ -125,4 +150,16 @@ class ExecTests extends AnyFunSuite with SnapshotAssertions {
125150

126151
valgrindCheck(code, "contrived-needs-sorting.c")("")
127152
}
153+
154+
test("asdf") {
155+
assert(GenerateTypes.toTypes(List.empty) == List.empty)
156+
}
157+
158+
test("Simple generated programs") {
159+
given PropertyCheckConfiguration =
160+
PropertyCheckConfiguration(minSize = 1, sizeRange = 5)
161+
forAll(GenerateTypes.genTypesAux().flatMap(GenerateTypes.genCode)) {
162+
parsedFile => valgrindCheck(parsedFile, "foo.c", None, snapshot = false)
163+
}
164+
}
128165
}
+89-25
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package fred
22

33
import scala.collection.mutable
4+
import scala.jdk.CollectionConverters.*
45

56
import org.scalacheck.Gen
67

78
object GenerateTypes {
8-
case class GeneratedType(refs: List[Int])
9+
private val SomeField = "value"
10+
11+
case class GenTypeAux(refs: List[Int])
912

1013
/** Generate a bunch of types. Each type is its own strongly-connected
1114
* component. The returned list is sorted backwards (types that come later in
@@ -17,69 +20,130 @@ object GenerateTypes {
1720
*
1821
* All references are mutable, for testing cycles.
1922
*/
20-
def genTypes(): Gen[List[GeneratedType]] = {
23+
def genTypesAux(): Gen[List[GenTypeAux]] = {
2124
Gen.choose(1, 40).flatMap { numSccs =>
2225
Gen.sequence(0.until(numSccs).map { i =>
23-
Gen.listOf(Gen.chooseNum(0, i)).map(GeneratedType(_))
26+
Gen.listOf(Gen.chooseNum(0, i)).map(GenTypeAux(_))
2427
})
2528
}
2629
}
2730

28-
def generateAst(typeAuxes: List[GeneratedType]): ParsedFile = {
29-
def nameFor(ind: Int) = s"T$ind"
31+
private def nameForType(ind: Int) = s"T$ind"
32+
33+
/** Name for the optional type corresponding to the type at index `ind` */
34+
private def nameForOptType(ind: Int) = s"OptT$ind"
35+
private def someCtorName(ind: Int) = s"Some$ind"
36+
private def noneCtorName(ind: Int) = s"None$ind"
3037

31-
/** Name for the optional type corresponding to the type at index `ind` */
32-
def nameForOpt(ind: Int) = s"OptT$ind"
38+
private def nameForField(ind: Int) = s"f$ind"
3339

34-
val types = typeAuxes.zipWithIndex.map { case (GeneratedType(refs), i) =>
35-
val typeName = Spanned(nameFor(i), Span.synthetic)
40+
def toTypes(typeAuxes: List[GenTypeAux]): List[TypeDef] = {
41+
val types = typeAuxes.zipWithIndex.map { case (GenTypeAux(refs), i) =>
42+
val typeName = Spanned(nameForType(i), Span.synth)
3643
TypeDef(
3744
typeName,
3845
List(
3946
EnumCase(
4047
typeName,
4148
refs.zipWithIndex.map { (typeInd, fieldInd) =>
4249
val fieldType =
43-
if (i == typeInd) nameForOpt(typeInd) else nameFor(typeInd)
50+
if (i == typeInd) nameForOptType(typeInd)
51+
else nameForType(typeInd)
4452
FieldDef(
4553
true,
46-
Spanned(s"f$fieldInd", Span.synthetic),
47-
TypeRef(fieldType, Span.synthetic),
48-
Span.synthetic
54+
Spanned(nameForField(fieldInd), Span.synth),
55+
TypeRef(fieldType, Span.synth),
56+
Span.synth
4957
)
5058
},
51-
Span.synthetic
59+
Span.synth
5260
)
5361
),
54-
Span.synthetic
62+
Span.synth
5563
)
5664
}
5765
// Generate optional types for self-referential types
5866
val optTypes = typeAuxes.zipWithIndex
59-
.filter { case (GeneratedType(refs), i) =>
67+
.filter { case (GenTypeAux(refs), i) =>
6068
refs.contains(i)
6169
}
6270
.map { (_, i) =>
63-
val name = Spanned(nameForOpt(i), Span.synthetic)
6471
TypeDef(
65-
name,
72+
Spanned(nameForOptType(i), Span.synth),
6673
List(
6774
EnumCase(
68-
name,
75+
Spanned(someCtorName(i), Span.synth),
6976
List(
7077
FieldDef(
7178
false,
72-
Spanned("value", Span.synthetic),
73-
TypeRef(nameFor(i), Span.synthetic),
74-
Span.synthetic
79+
Spanned(SomeField, Span.synth),
80+
TypeRef(nameForType(i), Span.synth),
81+
Span.synth
7582
)
7683
),
77-
Span.synthetic
84+
Span.synth
85+
),
86+
EnumCase(
87+
Spanned(noneCtorName(i), Span.synth),
88+
Nil,
89+
Span.synth
7890
)
7991
),
80-
Span.synthetic
92+
Span.synth
93+
)
94+
}
95+
types ++ optTypes
96+
}
97+
98+
def genCode(typeAuxes: List[GenTypeAux]): Gen[ParsedFile] = {
99+
val types = toTypes(typeAuxes)
100+
101+
def genExpr(typeInd: Int): Gen[Expr] = {
102+
val GenTypeAux(refs) = typeAuxes(typeInd)
103+
val fieldGens = refs.zipWithIndex.map { case (fieldTypeInd, fieldInd) =>
104+
val fieldName = Spanned(nameForField(fieldInd), Span.synth)
105+
val value =
106+
if (fieldTypeInd < typeInd) {
107+
genExpr(fieldTypeInd)
108+
} else {
109+
Gen.const(
110+
CtorCall(
111+
Spanned(noneCtorName(typeInd), Span.synth),
112+
Nil,
113+
Span.synth
114+
)
115+
)
116+
}
117+
value.map((fieldName, _))
118+
}
119+
// TODO Why in the world is this an ArrayList?
120+
Gen.sequence(fieldGens).map { fields =>
121+
CtorCall(
122+
Spanned(nameForType(typeInd), Span.synth),
123+
fields.asScala.toList,
124+
Span.synth
81125
)
82126
}
83-
ParsedFile(types ++ optTypes, Nil)
127+
}
128+
129+
genExpr(typeAuxes.length - 1).map { expr =>
130+
ParsedFile(
131+
types,
132+
List(
133+
FnDef(
134+
Spanned("main", Span.synth),
135+
Nil,
136+
TypeRef("int", Span.synth),
137+
LetExpr(
138+
Spanned("ignored", Span.synth),
139+
expr,
140+
IntLiteral(0, Span.synth),
141+
Span.synth
142+
),
143+
Span.synth
144+
)
145+
)
146+
)
147+
}
84148
}
85149
}

src/test/scala/fred/SCCTests.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ class SCCTests
1414
def createFile(graph: Map[String, Set[(Boolean, String)]]): ParsedFile = {
1515
ParsedFile(
1616
graph.map { (name, neighbors) =>
17-
val spannedName = Spanned(name, Span.synthetic)
17+
val spannedName = Spanned(name, Span.synth)
1818
val fields = graph(name).toList.map { (mutable, neighborName) =>
1919
FieldDef(
2020
mutable,
21-
Spanned(s"field$neighborName", Span.synthetic),
22-
TypeRef(neighborName, Span.synthetic),
23-
Span.synthetic
21+
Spanned(s"field$neighborName", Span.synth),
22+
TypeRef(neighborName, Span.synth),
23+
Span.synth
2424
)
2525
}
2626
TypeDef(
2727
spannedName,
2828
List(
29-
EnumCase(spannedName, fields, Span.synthetic)
29+
EnumCase(spannedName, fields, Span.synth)
3030
),
31-
Span.synthetic
31+
Span.synth
3232
)
3333
}.toList,
3434
Nil
@@ -118,8 +118,8 @@ class SCCTests
118118
}
119119

120120
test("Ensure strongly-connected components valid using Gen") {
121-
forAll(GenerateTypes.genTypes()) { typesAux =>
122-
val file = GenerateTypes.generateAst(typesAux)
121+
forAll(GenerateTypes.genTypesAux()) { typesAux =>
122+
val file = ParsedFile(GenerateTypes.toTypes(typesAux), Nil)
123123
val cycles = Cycles.fromFile(file)
124124

125125
assert(cycles.sccs.size === typesAux.size)

0 commit comments

Comments
 (0)