package uk.org.floop.sparqlTestRunner
import java.io._
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}
import org.apache.jena.query.{DatasetFactory, QueryExecutionFactory, QueryFactory, ResultSetFormatter}
import org.apache.jena.rdf.model.{Model, ModelFactory}
import org.apache.jena.riot.RDFDataMgr
import scala.io.Source
import scala.xml.{NodeSeq, PrettyPrinter}
case class Config(dir: File = new File("tests/sparql"),
report: File = new File("reports/TESTS-sparql-test-runner.xml"),
ignoreFail: Boolean = false,
data: Seq[File] = Seq())
object Run extends App {
val packageVersion: String = getClass.getPackage.getImplementationVersion()
val parser = new scopt.OptionParser[Config]("sparql-testrunner") {
head("sparql-testrunner", packageVersion)
opt[File]('t', "testdir") optional() valueName "<dir>" action { (x, c) =>
c.copy(dir = x)
} text "location of SPARQL queries to run, defaults to tests/sparql"
opt[File]('r', "report") optional() valueName "<report>" action { (x, c) =>
c.copy(report = x)
} text "file to output XML test report, defaults to reports/TESTS-sparql-test-runner.xml"
opt[Unit]('i', "ignorefail") optional() action { (_, c) =>
c.copy(ignoreFail = true)
} text "exit with success even if there are reported failures"
arg[File]("<file>...") unbounded() required() action { (x, c) =>
c.copy(data = c.data :+ x) } text "data to run the queries against"
}
parser.parse(args, Config()) match {
case Some(config) =>
val dataset = DatasetFactory.create
for (d <- config.data) {
RDFDataMgr.read(dataset, d.toString)
}
val union = ModelFactory.createDefaultModel
union.add(dataset.getDefaultModel)
union.add(dataset.getUnionModel)
dataset.close()
val (error, results) = runTestsUnder(config.dir, union, config.dir.toPath)
for (dir <- Option(config.report.getParentFile)) {
dir.mkdirs
}
val pp = new PrettyPrinter(80, 2)
val pw = new PrintWriter(config.report)
pw.write(pp.format(<testsuites>{results}</testsuites>))
pw.close
System.exit((error && !config.ignoreFail) match {
case true => 1
case false => 0
})
case None =>
}
def runTestsUnder(dir: File, model: Model, root: Path): (Boolean, NodeSeq) = {
var testSuites = NodeSeq.Empty
var testCases = NodeSeq.Empty
var overallError = false
var errors = 0
var skipped = 0
var tests = 0
val timeSuiteStart = System.currentTimeMillis()
var subSuiteTimes = 0L
for (f <- dir.listFiles) yield {
if (f.isDirectory) {
val subSuiteStart = System.currentTimeMillis()
val (error, subSuites) = runTestsUnder(f, model, root)
testSuites ++= subSuites
overallError |= error
subSuiteTimes += (System.currentTimeMillis() - subSuiteStart)
} else if (f.isFile && f.getName.endsWith(".sparql")) {
val timeTestStart = System.currentTimeMillis()
val relativePath = root.relativize(f.toPath).toString
val className = relativePath.substring(0, relativePath.lastIndexOf('.')).replace(File.pathSeparatorChar, '.')
val comment = {
val queryLines = Source.fromFile(f).getLines()
if (queryLines.hasNext) {
val line = queryLines.next()
if (line.startsWith("# "))
line.substring(2)
else
line
} else
className
}
tests += 1
val query = QueryFactory.create(new String(Files.readAllBytes(f.toPath), StandardCharsets.UTF_8))
val exec = QueryExecutionFactory.create(query, model)
if (query.isSelectType) {
var results = exec.execSelect()
var nonEmptyResults = results.hasNext()
val timeTaken = (System.currentTimeMillis() - timeTestStart).toFloat / 1000
testCases = testCases ++ <testcase name={comment} class={className} time={timeTaken.toString}>
{
val out = new ByteArrayOutputStream
ResultSetFormatter.outputAsCSV(out, results)
val actualResults = out.toString("utf-8")
val expect = new File(f.getPath.substring(0, f.getPath.lastIndexOf('.')) + ".expected")
if (expect.exists && expect.isFile) {
val expectedResults = new String(Files.readAllBytes(expect.toPath), StandardCharsets.UTF_8)
if (actualResults != expectedResults) {
errors += 1
System.err.println(s"Testcase $comment\nExpected:\n${expectedResults}\nActual:\n${actualResults}")
<error message={"Expected: \n" + expectedResults + "\nGot: \n" + actualResults}/>
}
} else {
// assume there should be no results
if (nonEmptyResults) {
errors += 1
System.err.println(s"Testcase $comment\nExpected empty result set, got:\n${actualResults}")
<failure message={s"Expected empty result set, got:\n${actualResults}"}/>
}
}
}
</testcase>
} else if (query.isAskType) {
val result = exec.execAsk()
val timeTaken = (System.currentTimeMillis() - timeTestStart).toFloat / 1000
testCases = testCases ++ <testcase name={comment} class={className} time={timeTaken.toString}>{
if (result) {
errors += 1
System.err.println(s"Testcase $comment\nExpected ASK query to return FALSE")
<failure message={"Constraint violated"}/>
}}</testcase>
} else {
skipped += 1
System.out.println(s"Skipped testcase $comment")
testCases = testCases ++ <testcase name={comment} class={className}>
<skipped/>
</testcase>
}
}
}
if (errors > 0) {
overallError = true
}
val testSuiteTime = (System.currentTimeMillis() - timeSuiteStart - subSuiteTimes).toFloat / 1000
val suiteName = {
val relativeName = root.getParent.relativize(dir.toPath).toString
if (relativeName.length == 0) {
"root"
} else {
relativeName
}
}
(overallError, testSuites ++ <testsuite errors={errors.toString} tests={tests.toString} time={testSuiteTime.toString}
name={suiteName} skipped={skipped.toString}>
{testCases}
</testsuite>)
}
}