-
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CanonicalDataParser (issue #331)
- Loading branch information
Showing
15 changed files
with
347 additions
and
99 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,4 @@ | ||
## Hints | ||
For this exercise two Scala features come in handy: | ||
- [Default Parameter Values](http://docs.scala-lang.org/tutorials/tour/default-parameter-values.html) | ||
- [String Interpolation](http://docs.scala-lang.org/overviews/core/string-interpolation.html) | ||
|
||
#### Common pitfalls that you should avoid | ||
- `null` is usually not considered a valid value in Scala, and there are no `null` checks needed (if you don't have to interface with Java code, say). Instead there is the [Option](http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html) type if you want to express the possible absence of a value. But for this exercise just assume a normal non-`null` String parameter. | ||
- Usually there is no need in Scala to use `return`. For a discussion see [here](http://stackoverflow.com/questions/24856106/return-in-a-scala-function-literal). Or as a quote from that discussion: *Don't use return, it makes Scala cry.* |
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 |
---|---|---|
@@ -1,5 +1,4 @@ | ||
object HelloWorld { | ||
def hello() = "Hello, World!" | ||
|
||
def hello(name: String) = s"Hello, $name!" | ||
} | ||
|
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 |
---|---|---|
@@ -1,17 +1,10 @@ | ||
import org.scalatest.{Matchers, FunSuite} | ||
|
||
/** @version 1.0.0 */ | ||
class HelloWorldTest extends FunSuite with Matchers { | ||
test("Without name") { | ||
HelloWorld.hello() should be ("Hello, World!") | ||
} | ||
|
||
test("with name") { | ||
pending | ||
HelloWorld.hello("Jane") should be ("Hello, Jane!") | ||
} | ||
|
||
test("with umlaut name") { | ||
pending | ||
HelloWorld.hello("Jürgen") should be ("Hello, Jürgen!") | ||
test("Say Hi!") { | ||
HelloWorld.hello() should be ("Hello, World!") | ||
} | ||
} | ||
|
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 |
---|---|---|
@@ -0,0 +1,2 @@ | ||
sbt.version=0.13.13 | ||
|
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,2 @@ | ||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.0") | ||
|
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,15 @@ | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object BeerSongTestGenerator { | ||
def main(args: Array[String]): Unit = { | ||
val file = new File("src/main/resources/beer-song.json") | ||
|
||
val code = TestSuiteBuilder.build(file, | ||
fromLabeledTestAlt("verse" -> Seq("number"), "verses" -> Seq("beginning", "end"))) | ||
println(s"-------------") | ||
println(code) | ||
println(s"-------------") | ||
} | ||
} |
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 |
---|---|---|
@@ -1,49 +1,28 @@ | ||
import play.api.libs.json.Json | ||
|
||
import scala.io.Source | ||
|
||
class BowlingTestGenerator { | ||
implicit val testCaseReader = Json.reads[BowlingTestCase] | ||
|
||
private val filename = "bowling.json" | ||
private val fileContents = Source.fromFile(filename).getLines.mkString | ||
private val json = Json.parse(fileContents) | ||
|
||
def write { | ||
val testCases = (json \ "score" \ "cases").get.as[List[BowlingTestCase]] | ||
val description = (json \ "score" \ "description").get.as[List[String]].mkString(" ") | ||
|
||
implicit def testCaseToGen(tc: BowlingTestCase): TestCaseGen = { | ||
val callSUT = | ||
s"${tc.rolls}.foldLeft(Bowling())((acc, roll) => acc.roll(roll)).score()" | ||
val expected = "" | ||
val result = s"val score = $callSUT" | ||
val (matchRight, matchLeft) = | ||
if (tc.expected == -1) | ||
("""fail("Unexpected score returned. Failure expected")""", "") | ||
else | ||
(s"assert(n == ${tc.expected})", s"""fail("${tc.description}")""") | ||
val checkResult = | ||
s"""score match { | ||
case Right(n) => $matchRight | ||
case Left(_) => $matchLeft | ||
}""" | ||
|
||
TestCaseGen(tc.description, callSUT, expected, result, checkResult) | ||
} | ||
|
||
val testBuilder = new TestBuilder("BowlingTest") | ||
testBuilder.addTestCases(testCases, Some(description)) | ||
testBuilder.toFile | ||
} | ||
} | ||
|
||
case class BowlingTestCase(description: String, | ||
rolls: List[Int], | ||
expected: Int) | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object BowlingTestGenerator { | ||
def main(args: Array[String]): Unit = { | ||
new BowlingTestGenerator().write | ||
val file = new File("src/main/resources/bowling.json") | ||
|
||
def fromLabeledTest(argNames: String*): ToTestCaseData = | ||
withLabeledTest { sut => labeledTest => | ||
val args = sutArgs(labeledTest.result, argNames: _*) | ||
val isDefined = | ||
labeledTest.expected.fold(Function.const(".isDefined"), Function.const("")) | ||
val sutCall = | ||
s"""val score = ${args}.foldLeft(Bowling())((acc, roll) => acc.roll(roll)).score() | ||
score$isDefined""" | ||
val expected = | ||
labeledTest.expected.fold(Function.const("true"), x => s"Some($x)") | ||
|
||
TestCaseData(labeledTest.description, sutCall, expected) | ||
} | ||
|
||
val code = TestSuiteBuilder.build(file, fromLabeledTest("previous_rolls")) | ||
println(s"-------------") | ||
println(code) | ||
println(s"-------------") | ||
} | ||
} |
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,14 @@ | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object HelloWorldTestGenerator { | ||
def main(args: Array[String]): Unit = { | ||
val file = new File("src/main/resources/hello-world.json") | ||
|
||
val code = TestSuiteBuilder.build(file, fromLabeledTest()) | ||
println(s"-------------") | ||
println(code) | ||
println(s"-------------") | ||
} | ||
} |
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,15 @@ | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object NucleotideCountTestGenerator { | ||
|
||
def main(args: Array[String]): Unit = { | ||
val file = new File("src/main/resources/nucleotide-count.json") | ||
|
||
val code = TestSuiteBuilder.build(file, fromLabeledTest("strand")) | ||
println(s"-------------") | ||
println(code) | ||
println(s"-------------") | ||
} | ||
} |
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 |
---|---|---|
@@ -1,43 +1,25 @@ | ||
import play.api.libs.json.Json | ||
|
||
import scala.io.Source | ||
|
||
// Generates test suite from json definition for the Panframs exercise. | ||
class PangramsTestGenerator { | ||
implicit val pangramTestCaseReader = Json.reads[PangramTestCase] | ||
|
||
private val filename = "pangram.json" | ||
private val fileContents = Source.fromFile(filename).getLines.mkString | ||
private val json = Json.parse(fileContents) | ||
|
||
def write { | ||
print("import org.scalatest.{FunSuite, Matchers}" + System.lineSeparator()) | ||
print(System.lineSeparator()) | ||
print("class PangramsTest extends FunSuite with Matchers {" + System.lineSeparator()) | ||
|
||
writeTestCases() | ||
|
||
print("}" + System.lineSeparator()) | ||
} | ||
|
||
private def writeTestCases(): Unit = { | ||
val testCases = (json \ "cases").get.as[List[PangramTestCase]] | ||
|
||
testCases.foreach(tc => { | ||
print("\ttest(\"" + tc.description + "\") {" + System.lineSeparator()) | ||
|
||
println("Pangrams.isPangram(\"" + tc.input + "\") should be (" + tc.expected + ")") | ||
|
||
print("\t}" + System.lineSeparator()) | ||
print(System.lineSeparator()) | ||
}) | ||
} | ||
} | ||
|
||
case class PangramTestCase(description: String, input: String, expected: Boolean) | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object PangramsTestGenerator { | ||
def main(args: Array[String]): Unit = { | ||
new PangramsTestGenerator().write | ||
val file = new File("src/main/resources/pangram.json") | ||
def fromLabeledTest(argNames: String*): ToTestCaseData = | ||
withLabeledTest { sut => | ||
labeledTest => | ||
val args = sutArgs(labeledTest.result, argNames: _*) | ||
val isPangram = labeledTest.property.mkString | ||
val sutCall = | ||
s"""Pangrams.$isPangram($args)""" | ||
val expected = | ||
labeledTest.expected.fold(Function.const("true"), x => s"$x") | ||
TestCaseData(labeledTest.description, sutCall, expected) | ||
} | ||
|
||
val code = TestSuiteBuilder.build(file, fromLabeledTest("input")) | ||
println(s"‐‐‐‐‐‐‐‐‐‐‐‐‐") | ||
println(code) | ||
println(s"‐‐‐‐‐‐‐‐‐‐‐‐‐") | ||
} | ||
} |
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,14 @@ | ||
import testgen._ | ||
import TestSuiteBuilder._ | ||
import java.io.File | ||
|
||
object SumOfMultiplesTestGenerator { | ||
def main(args: Array[String]): Unit = { | ||
val file = new File("src/main/resources/sum-of-multiples.json") | ||
|
||
val code = TestSuiteBuilder.build(file, fromLabeledTest("factors", "limit")) | ||
println(s"-------------") | ||
println(code) | ||
println(s"-------------") | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
testgen/src/main/scala/testgen/CanonicalDataParser.scala
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,105 @@ | ||
package testgen | ||
|
||
import scala.io.Source | ||
import scala.util.parsing.json.JSON | ||
import CanonicalDataParser._ | ||
import scala.util.Try | ||
import scala.Left | ||
import scala.Right | ||
import java.io.File | ||
|
||
object CanonicalDataParser { | ||
type ParseResult = Map[String,Any] | ||
|
||
type Description = String | ||
type Comments = Seq[String] | ||
type Cases = Seq[LabeledTestItem] | ||
type Property = String | ||
type Result = Any | ||
type Error = String | ||
type Expected = Either[Error, Result] | ||
type Properties = Option[Map[String,Any]] | ||
|
||
def getOptional[T](result: ParseResult, key: String): Option[T] = | ||
result.get(key).asInstanceOf[Option[T]] | ||
def getRequired[T](result: ParseResult, key: String): T = | ||
getOptional(result, key) getOrElse (throw new Exception(s"missing: $key")) | ||
|
||
def parse(file: File): Exercise = { | ||
val fileContents = Source.fromFile(file).getLines.mkString | ||
val rawParseResult = | ||
JSON.parseFull(fileContents).get.asInstanceOf[ParseResult] | ||
val parseResult = rawParseResult mapValues restoreInts | ||
println(parseResult) | ||
parseResult | ||
} | ||
|
||
private def restoreInts(any: Any): Any = | ||
any match { | ||
case double: Double if (double.toInt.toDouble == double) => double.toInt | ||
case map: Map[_,_] => map mapValues restoreInts | ||
case seq: Seq[_] => seq map restoreInts | ||
case any => any | ||
} | ||
|
||
def main(args: Array[String]): Unit = { | ||
val path = "src/main/resources" | ||
// val name = "hello-world.json" | ||
// val name = "sum-of-multiples.json" | ||
val name = "bowling.json" | ||
val result = parse(new File(s"$path/$name")) | ||
println(result) | ||
} | ||
} | ||
|
||
case class Exercise(name: String, version: String, cases: Cases, | ||
comments: Option[Comments]) | ||
object Exercise { | ||
implicit def fromParseResult(result: ParseResult): Exercise = { | ||
val cases: Cases = | ||
getRequired[Seq[ParseResult]](result, "cases") map LabeledTestItem.fromParseResult | ||
Exercise(getRequired(result, "exercise"), getRequired(result, "version"), | ||
flattenCases(cases), getOptional(result, "comments")) | ||
} | ||
|
||
// so far there are to few LabeledTestGroups to handle them separately | ||
private def flattenCases(cases: Cases): Cases = | ||
cases match { | ||
case Seq() => Seq() | ||
case (ltg: LabeledTestGroup) +: xs => ltg.cases ++ flattenCases(xs) | ||
case (lt: LabeledTest) +: xs => lt +: flattenCases(xs) | ||
} | ||
} | ||
|
||
sealed trait LabeledTestItem | ||
object LabeledTestItem { | ||
implicit def fromParseResult(result: ParseResult): LabeledTestItem = | ||
if (result.contains("cases")) result: LabeledTestGroup | ||
else result: LabeledTest | ||
} | ||
|
||
case class LabeledTest(description: Description, property: Property, | ||
expected: Expected, result: ParseResult) extends LabeledTestItem | ||
object LabeledTest { | ||
implicit def fromParseResult(result: ParseResult): LabeledTest = { | ||
val expected: Expected = { | ||
val any = getRequired[Any](result, "expected") | ||
val error = Try { | ||
Left(any.asInstanceOf[Map[String,String]]("error")) | ||
} | ||
error.getOrElse(Right(any)) | ||
} | ||
LabeledTest(getRequired(result, "description"), getRequired(result, "property"), | ||
expected, result) | ||
} | ||
} | ||
|
||
case class LabeledTestGroup(description: Description, cases: Cases) extends LabeledTestItem | ||
object LabeledTestGroup { | ||
implicit def fromParseResult(result: ParseResult): LabeledTestGroup = { | ||
val description = getRequired[String](result, "description") | ||
val cases = | ||
getRequired[Seq[ParseResult]](result, "cases") map LabeledTestItem.fromParseResult | ||
LabeledTestGroup(description, cases) | ||
} | ||
} |
Oops, something went wrong.