Skip to content

Commit

Permalink
Merge pull request #1176 from bjaglin/scalatest
Browse files Browse the repository at this point in the history
make it possible to use testkit with ScalaTest 3.2.x
  • Loading branch information
github-brice-jaglin authored Jun 30, 2020
2 parents e51329f + 61d44a3 commit 62dc440
Show file tree
Hide file tree
Showing 37 changed files with 273 additions and 217 deletions.
4 changes: 4 additions & 0 deletions .scala-steward.conf
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pullRequests.frequency = "0 0 1,15 * ?"
updates.pin = [
# don't bump, to avoid forcing breaking changes on clients via eviction
{ groupId = "org.scalatest", artifactId = "scalatest", version = "3.0.8" },
]
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ lazy val unit = project
libraryDependencies ++= List(
jgit,
semanticdbPluginLibrary,
scalatest,
scalatest.withRevision("3.2.0"), // make sure testkit clients can use recent 3.x versions
"org.scalameta" %% "testkit" % scalametaV
),
compileInputs.in(Compile, compile) := {
Expand Down
7 changes: 5 additions & 2 deletions docs/developers/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ artifact
cs fetch ch.epfl.scala:scalafix-testkit_@SCALA212@:@VERSION@
```

Next, create a test suite that extends the class `SemanticRuleSuite`
Next, create a test suite that extends `AbstractSemanticRuleSuite`

```scala
package myproject
class MyTests extends scalafix.testkit.SemanticRuleSuite {
class MyTests
extends scalafix.testkit.AbstractSemanticRuleSuite
with org.scalatest.funsuite.AnyFunSuiteLike {

runAllTests()
}
```
Expand Down
3 changes: 2 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ object Dependencies {
def metacp = "org.scalameta" %% "metacp" % scalametaV
def semanticdbPluginLibrary = "org.scalameta" % "semanticdb-scalac-core" % scalametaV cross CrossVersion.full
def scalameta = "org.scalameta" %% "scalameta" % scalametaV
def scalatest = "org.scalatest" %% "scalatest" % "3.0.8"
def scalatest =
"org.scalatest" %% "scalatest" % "3.0.8" // don't bump, to avoid forcing breaking changes on clients via eviction
def bijectionCore = "com.twitter" %% "bijection-core" % "0.9.7"
def scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.3"
def collectionCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6"
Expand Down
2 changes: 2 additions & 0 deletions project/Mima.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ object Mima {
ProblemFilters.exclude[DirectMissingMethodProblem]("scalafix.testkit.SemanticRuleSuite.LintAssertion"),
ProblemFilters.exclude[MissingClassProblem]("scalafix.testkit.package$"),
ProblemFilters.exclude[MissingClassProblem]("scalafix.testkit.package"),
ProblemFilters.exclude[MissingTypesProblem]("scalafix.testkit.DiffAssertions"),
ProblemFilters.exclude[MissingTypesProblem]("scalafix.testkit.SemanticRuleSuite"),
// marked private[scalafix]
ProblemFilters.exclude[ReversedMissingMethodProblem]("scalafix.rule.RuleCtx.printLintMessage"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scalafix.rule.RuleCtx.filter"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package scalafix.testkit

import java.nio.charset.StandardCharsets
import java.nio.file.Files

import org.scalatest.exceptions.TestFailedException
import org.scalatest.{BeforeAndAfterAll, Suite, TestRegistration}
import scalafix.internal.reflect.ClasspathOps
import scalafix.internal.testkit.{AssertDiff, CommentAssertion}

import scala.meta._
import scala.meta.internal.io.FileIO

/** Construct a test suite for running semantic Scalafix rules.
* <p>
* Mix-in FunSuiteLike (ScalaTest 3.0), AnyFunSuiteLike (ScalaTest 3.1+) or
* the testing style of your choice if you add your own tests.
*/
abstract class AbstractSemanticRuleSuite(
val props: TestkitProperties,
val isSaveExpect: Boolean
) extends Suite
with TestRegistration
with DiffAssertions
with BeforeAndAfterAll { self =>

def this(props: TestkitProperties) = this(props, isSaveExpect = false)
def this() = this(TestkitProperties.loadFromResources())

private def scalaVersion: String = scala.util.Properties.versionNumberString
private def scalaVersionDirectory: Option[String] =
if (scalaVersion.startsWith("2.11")) Some("scala-2.11")
else if (scalaVersion.startsWith("2.12")) Some("scala-2.12")
else if (scalaVersion.startsWith("2.13")) Some("scala-2.13")
else None

def evaluateTestBody(diffTest: RuleTest): Unit = {
val (rule, sdoc) = diffTest.run.apply()
rule.beforeStart()
val (fixed, messages) =
try rule.semanticPatch(sdoc, suppress = false)
finally rule.afterComplete()
val tokens = fixed.tokenize.get
val obtained = SemanticRuleSuite.stripTestkitComments(tokens)
val expected = diffTest.path.resolveOutput(props) match {
case Right(file) =>
FileIO.slurp(file, StandardCharsets.UTF_8)
case Left(err) =>
if (fixed == sdoc.input.text) {
// rule is a linter, no need for an output file.
obtained
} else {
fail(err)
}
}

val expectedLintMessages = CommentAssertion.extract(sdoc.tokens)
val diff = AssertDiff(messages, expectedLintMessages)

if (diff.isFailure) {
println("###########> Lint <###########")
println(diff.toString)
}

val result = compareContents(obtained, expected)
if (result.nonEmpty) {
println("###########> Diff <###########")
println(error2message(obtained, expected))
}

val isTestFailure = result.nonEmpty || diff.isFailure
diffTest.path.resolveOutput(props) match {
case Right(output) if isTestFailure && isSaveExpect =>
println(s"promoted expect test: $output")
Files.write(output.toNIO, obtained.getBytes(StandardCharsets.UTF_8))
case _ =>
}

if (isTestFailure) {
throw new TestFailedException("see above", 0)
}
}

def runOn(diffTest: RuleTest): Unit = {
registerTest(diffTest.path.testName) {
evaluateTestBody(diffTest)
}
}

lazy val testsToRun = {
val symtab = ClasspathOps.newSymbolTable(props.inputClasspath)
val classLoader = ClasspathOps.toClassLoader(props.inputClasspath)
val tests = TestkitPath.fromProperties(props)
tests.map { test =>
RuleTest.fromPath(props, test, classLoader, symtab)
}
}
def runAllTests(): Unit = {
testsToRun.foreach(runOn)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package scalafix.testkit

import org.scalatest.{Suite, TestRegistration}
import org.scalatest.Tag
import scala.meta._
import scalafix.syntax._
import scalafix.v0._
import scalafix.internal.config.ScalafixConfig

/** Utility to unit test syntactic rules.
* <p>
* Mix-in FunSuiteLike (ScalaTest 3.0), AnyFunSuiteLike (ScalaTest 3.1+) or
* the testing style of your choice if you add your own tests.
*
* @param rule the default rule to use from `check`/`checkDiff`.
*/
abstract class AbstractSyntacticRuleSuite(rule: Rule = Rule.empty)
extends Suite
with TestRegistration
with DiffAssertions {

def check(name: String, original: String, expected: String): Unit = {
check(rule, name, original, expected)
}

def check(
rule: Rule,
name: String,
original: String,
expected: String
): Unit = {
check(rule, name, original, expected, Seq(): _*)
}

def check(
rule: Rule,
name: String,
original: String,
expected: String,
testTags: Tag*
): Unit = {
registerTest(name, testTags: _*) {
import scala.meta._
val obtained = rule.apply(Input.String(original))
assertNoDiff(obtained, expected)
}
}

def checkDiff(original: Input, expected: String): Unit = {
checkDiff(rule, original, expected, Seq(): _*)
}

def checkDiff(original: Input, expected: String, testTags: Tag*): Unit = {
checkDiff(rule, original, expected, testTags: _*)
}

def checkDiff(rule: Rule, original: Input, expected: String): Unit = {
checkDiff(rule, original, expected, Seq(): _*)
}

def checkDiff(
rule: Rule,
original: Input,
expected: String,
testTags: Tag*
): Unit = {
registerTest(original.label, testTags: _*) {
val dialect = ScalafixConfig.default.parser.dialectForFile("Source.scala")
val ctx = RuleCtx(dialect(original).parse[Source].get)
val obtained = rule.diff(ctx)
assertNoDiff(obtained, expected)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.TimeZone

import org.scalatest.FunSuiteLike
import org.scalatest.Suite
import org.scalatest.exceptions.TestFailedException

object DiffAssertions {
Expand Down Expand Up @@ -36,7 +36,7 @@ object DiffAssertions {
}
}

trait DiffAssertions extends FunSuiteLike {
trait DiffAssertions extends Suite {

def assertEqual[A](a: A, b: A): Unit = {
assert(a === b)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
package scalafix.testkit

import java.nio.charset.StandardCharsets
import org.scalatest.BeforeAndAfterAll
import org.scalatest.FunSuite
import org.scalatest.exceptions.TestFailedException
import scala.meta._
import scala.meta.internal.io.FileIO
import scalafix.internal.reflect.ClasspathOps
import org.scalatest.FunSpecLike
import scalafix.internal.reflect.RuleCompiler
import scalafix.internal.testkit.AssertDiff
import scalafix.internal.testkit.CommentAssertion
import scalafix.internal.testkit.EndOfLineAssertExtractor
import scalafix.internal.testkit.MultiLineAssertExtractor
import scalafix.internal.testkit.{
EndOfLineAssertExtractor,
MultiLineAssertExtractor
}
import scalafix.v0.SemanticdbIndex
import java.nio.file.Files

/** Construct a test suite for running semantic Scalafix rules. */
abstract class SemanticRuleSuite(
val props: TestkitProperties,
val isSaveExpect: Boolean
) extends FunSuite
with DiffAssertions
with BeforeAndAfterAll { self =>

@deprecated(
"Use AbstractSemanticRuleSuite with the styling trait of your choice mixed-in (*SpecLike or *SuiteLike)",
"0.9.18"
)
class SemanticRuleSuite(
override val props: TestkitProperties,
override val isSaveExpect: Boolean
) extends AbstractSemanticRuleSuite
with FunSpecLike {
def this(props: TestkitProperties) = this(props, isSaveExpect = false)
def this() = this(TestkitProperties.loadFromResources())

Expand All @@ -35,78 +30,6 @@ abstract class SemanticRuleSuite(
inputSourceroot: AbsolutePath,
expectedOutputSourceroot: Seq[AbsolutePath]
) = this()

private def scalaVersion: String = scala.util.Properties.versionNumberString
private def scalaVersionDirectory: Option[String] =
if (scalaVersion.startsWith("2.11")) Some("scala-2.11")
else if (scalaVersion.startsWith("2.12")) Some("scala-2.12")
else if (scalaVersion.startsWith("2.13")) Some("scala-2.13")
else None

def evaluateTestBody(diffTest: RuleTest): Unit = {
val (rule, sdoc) = diffTest.run.apply()
rule.beforeStart()
val (fixed, messages) =
try rule.semanticPatch(sdoc, suppress = false)
finally rule.afterComplete()
val tokens = fixed.tokenize.get
val obtained = SemanticRuleSuite.stripTestkitComments(tokens)
val expected = diffTest.path.resolveOutput(props) match {
case Right(file) =>
FileIO.slurp(file, StandardCharsets.UTF_8)
case Left(err) =>
if (fixed == sdoc.input.text) {
// rule is a linter, no need for an output file.
obtained
} else {
fail(err)
}
}

val expectedLintMessages = CommentAssertion.extract(sdoc.tokens)
val diff = AssertDiff(messages, expectedLintMessages)

if (diff.isFailure) {
println("###########> Lint <###########")
println(diff.toString)
}

val result = compareContents(obtained, expected)
if (result.nonEmpty) {
println("###########> Diff <###########")
println(error2message(obtained, expected))
}

val isTestFailure = result.nonEmpty || diff.isFailure
diffTest.path.resolveOutput(props) match {
case Right(output) if isTestFailure && isSaveExpect =>
println(s"promoted expect test: $output")
Files.write(output.toNIO, obtained.getBytes(StandardCharsets.UTF_8))
case _ =>
}

if (isTestFailure) {
throw new TestFailedException("see above", 0)
}
}

def runOn(diffTest: RuleTest): Unit = {
test(diffTest.path.testName) {
evaluateTestBody(diffTest)
}
}

lazy val testsToRun = {
val symtab = ClasspathOps.newSymbolTable(props.inputClasspath)
val classLoader = ClasspathOps.toClassLoader(props.inputClasspath)
val tests = TestkitPath.fromProperties(props)
tests.map { test =>
RuleTest.fromPath(props, test, classLoader, symtab)
}
}
def runAllTests(): Unit = {
testsToRun.foreach(runOn)
}
}

object SemanticRuleSuite {
Expand Down
Loading

0 comments on commit 62dc440

Please sign in to comment.