diff --git a/build.sbt b/build.sbt index 39b149e3..a804b752 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,3 @@ -import com.typesafe.tools.mima.core._ import sbtcrossproject.CrossPlugin.autoImport.crossProject import sbtcrossproject.CrossPlugin.autoImport.CrossType import scala.collection.mutable @@ -66,81 +65,7 @@ def isScala3(v: Option[(Long, Long)]): Boolean = v.exists(_._1 == 3) lazy val skipIdeaSettings = SettingKey[Boolean]("ide-skip-project").withRank(KeyRanks.Invisible) := true lazy val mimaEnable: List[Def.Setting[_]] = List( - mimaBinaryIssueFilters ++= List( - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.MUnitRunner.descriptions" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.MUnitRunner.testNames" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.MUnitRunner.munitTests" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.ValueTransforms.munitTimeout" - ), - ProblemFilters.exclude[MissingTypesProblem]("munit.FailException"), - ProblemFilters.exclude[MissingTypesProblem]("munit.FailSuiteException"), - ProblemFilters.exclude[MissingTypesProblem]( - "munit.TestValues$FlakyFailure" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.internal.junitinterface.JUnitComputer.this" - ), - // Known breaking changes for MUnit v1 - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.Assertions.assertNotEquals" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.Assertions.assertEquals" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.Assertions.assertNotEquals" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.Assertions.assertEquals" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.FunSuite.assertNotEquals" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.FunSuite.assertEquals" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.FunSuite.munitTestTransform" - ), - ProblemFilters.exclude[MissingClassProblem]("munit.GenericAfterEach"), - ProblemFilters.exclude[MissingClassProblem]("munit.GenericBeforeEach"), - ProblemFilters.exclude[MissingClassProblem]("munit.GenericTest"), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.MUnitRunner.createTestDescription" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.Suite.beforeEach" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.Suite.afterEach" - ), - ProblemFilters.exclude[MissingClassProblem]("munit.Suite$Fixture"), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.TestTransforms#TestTransform.apply" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.FunFixtures#FunFixture.this" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.SuiteTransforms#SuiteTransform.this" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.TestTransforms#TestTransform.this" - ), - ProblemFilters.exclude[IncompatibleMethTypeProblem]( - "munit.ValueTransforms#ValueTransform.this" - ), - ProblemFilters.exclude[DirectMissingMethodProblem]( - "munit.ScalaCheckSuite.unitToProp" - ) - ), + mimaBinaryIssueFilters ++= MimaExclusions.list, mimaPreviousArtifacts := { if (crossPaths.value) Set("org.scalameta" %% moduleName.value % previousVersion) @@ -246,6 +171,8 @@ lazy val munit = crossProject(JSPlatform, JVMPlatform, NativePlatform) ) ) .jvmConfigure(_.dependsOn(junit)) + .dependsOn(munitDiff) + lazy val munitJVM = munit.jvm lazy val munitJS = munit.js lazy val munitNative = munit.native @@ -270,6 +197,30 @@ lazy val plugin = project ) .disablePlugins(MimaPlugin) +lazy val munitDiff = crossProject(JSPlatform, JVMPlatform, NativePlatform) + .in(file("munit-diff")) + .settings( + moduleName := "munit-diff", + sharedSettings, + libraryDependencies ++= List( + "org.scala-lang" % "scala-reflect" % { + if (isScala3Setting.value) scala213 + else scalaVersion.value + } % Provided + ) + ) + .jvmSettings( + sharedJVMSettings + ) + .nativeConfigure(sharedNativeConfigure) + .nativeSettings( + sharedNativeSettings + ) + .jsConfigure(sharedJSConfigure) + .jsSettings(sharedJSSettings) + // TODO Reenable on 1.0.0 + .disablePlugins(MimaPlugin) + lazy val tests = crossProject(JSPlatform, JVMPlatform, NativePlatform) .dependsOn(munit) .enablePlugins(BuildInfoPlugin) diff --git a/docs/tests.md b/docs/tests.md index a8e9f401..9d347d23 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -132,7 +132,7 @@ Override `printer` to customize the comparison of two values : ```scala mdoc import java.time.Instant import munit.FunSuite -import munit.Printer +import munit.diff.Printer class CompareDatesOnlyTest extends FunSuite { override val printer = Printer.apply { @@ -152,7 +152,7 @@ or to customize the printed clue in case of a failure : ```scala mdoc import munit.FunSuite -import munit.Printer +import munit.diff.Printer class CustomListOfCharPrinterTest extends FunSuite { override val printer = Printer.apply { diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Chunk.scala b/munit-diff/shared/src/main/scala/munit/diff/Chunk.scala similarity index 89% rename from munit/shared/src/main/scala/munit/internal/difflib/Chunk.scala rename to munit-diff/shared/src/main/scala/munit/diff/Chunk.scala index c31a7ac3..cb7496ff 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/Chunk.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Chunk.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff import java.util diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Delta.scala b/munit-diff/shared/src/main/scala/munit/diff/Delta.scala similarity index 96% rename from munit/shared/src/main/scala/munit/internal/difflib/Delta.scala rename to munit-diff/shared/src/main/scala/munit/diff/Delta.scala index 13baccd6..d0c6fd30 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/Delta.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Delta.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff sealed abstract class Delta[T](original: Chunk[T], revised: Chunk[T]) { diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Diff.scala b/munit-diff/shared/src/main/scala/munit/diff/Diff.scala similarity index 93% rename from munit/shared/src/main/scala/munit/internal/difflib/Diff.scala rename to munit-diff/shared/src/main/scala/munit/diff/Diff.scala index 80369302..57996767 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/Diff.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Diff.scala @@ -1,7 +1,7 @@ -package munit.internal.difflib +package munit.diff -import munit.internal.console.{AnsiColors, Printers} -import munit.internal.difflib +import munit.diff.console.Printers +import munit.diff.console.AnsiColors import scala.collection.JavaConverters._ @@ -74,11 +74,11 @@ class Diff(val obtained: String, val expected: String) extends Serializable { original: Seq[String], revised: Seq[String] ): String = { - val diff = difflib.DiffUtils.diff(original.asJava, revised.asJava) + val diff = DiffUtils.diff(original.asJava, revised.asJava) val result = if (diff.getDeltas.isEmpty) "" else { - difflib.DiffUtils + DiffUtils .generateUnifiedDiff( "obtained", "expected", diff --git a/munit/shared/src/main/scala/munit/internal/difflib/DiffAlgorithm.scala b/munit-diff/shared/src/main/scala/munit/diff/DiffAlgorithm.scala similarity index 78% rename from munit/shared/src/main/scala/munit/internal/difflib/DiffAlgorithm.scala rename to munit-diff/shared/src/main/scala/munit/diff/DiffAlgorithm.scala index 63639665..46438ca4 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/DiffAlgorithm.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/DiffAlgorithm.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff import java.util diff --git a/munit/shared/src/main/scala/munit/internal/difflib/DiffUtils.scala b/munit-diff/shared/src/main/scala/munit/diff/DiffUtils.scala similarity index 99% rename from munit/shared/src/main/scala/munit/internal/difflib/DiffUtils.scala rename to munit-diff/shared/src/main/scala/munit/diff/DiffUtils.scala index 2292f37d..79c9e1e3 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/DiffUtils.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/DiffUtils.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff import java.util diff --git a/munit/shared/src/main/scala/munit/internal/difflib/DifferentiationFailedException.scala b/munit-diff/shared/src/main/scala/munit/diff/DifferentiationFailedException.scala similarity index 72% rename from munit/shared/src/main/scala/munit/internal/difflib/DifferentiationFailedException.scala rename to munit-diff/shared/src/main/scala/munit/diff/DifferentiationFailedException.scala index 9e2c327b..ce57e979 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/DifferentiationFailedException.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/DifferentiationFailedException.scala @@ -1,3 +1,3 @@ -package munit.internal.difflib +package munit.diff class DifferentiationFailedException(message: String) extends Exception(message) diff --git a/munit-diff/shared/src/main/scala/munit/diff/Diffs.scala b/munit-diff/shared/src/main/scala/munit/diff/Diffs.scala new file mode 100644 index 00000000..828084c7 --- /dev/null +++ b/munit-diff/shared/src/main/scala/munit/diff/Diffs.scala @@ -0,0 +1,28 @@ +package munit.diff + +object Diffs { + + def create(obtained: String, expected: String): Diff = + new Diff(obtained, expected) + + def createDiffOnlyReport( + obtained: String, + expected: String + ): String = { + create(obtained, expected).createDiffOnlyReport() + } + + def createReport( + obtained: String, + expected: String, + title: String, + printObtainedAsStripMargin: Boolean = true + ): String = { + create(obtained, expected).createReport(title, printObtainedAsStripMargin) + } + + def unifiedDiff(obtained: String, expected: String): String = { + create(obtained, expected).unifiedDiff + } + +} diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Equalizer.scala b/munit-diff/shared/src/main/scala/munit/diff/Equalizer.scala similarity index 88% rename from munit/shared/src/main/scala/munit/internal/difflib/Equalizer.scala rename to munit-diff/shared/src/main/scala/munit/diff/Equalizer.scala index 4c5037b5..d7136f67 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/Equalizer.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Equalizer.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff trait Equalizer[T] { def equals(original: T, revised: T): Boolean diff --git a/munit/shared/src/main/scala/munit/internal/difflib/MyersDiff.scala b/munit-diff/shared/src/main/scala/munit/diff/MyersDiff.scala similarity index 83% rename from munit/shared/src/main/scala/munit/internal/difflib/MyersDiff.scala rename to munit-diff/shared/src/main/scala/munit/diff/MyersDiff.scala index 5d2d2404..5304b196 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/MyersDiff.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/MyersDiff.scala @@ -1,29 +1,29 @@ -package munit.internal.difflib +package munit.diff import java.util class MyersDiff[T](equalizer: Equalizer[T]) - extends munit.internal.difflib.DiffAlgorithm[T] { + extends munit.diff.DiffAlgorithm[T] { def this() = this(Equalizer.default[T]) override def diff( original: util.List[T], revised: util.List[T] - ): munit.internal.difflib.Patch[T] = { + ): munit.diff.Patch[T] = { try { buildRevision(buildPath(original, revised), original, revised) } catch { case e: DifferentiationFailedException => e.printStackTrace() - new munit.internal.difflib.Patch[T]() + new munit.diff.Patch[T]() } } private def buildRevision( _path: PathNode, orig: util.List[T], rev: util.List[T] - ): munit.internal.difflib.Patch[T] = { + ): munit.diff.Patch[T] = { var path = _path - val patch = new munit.internal.difflib.Patch[T] + val patch = new munit.diff.Patch[T] if (path.isSnake) path = path.prev while ( path != null && @@ -40,22 +40,22 @@ class MyersDiff[T](equalizer: Equalizer[T]) val ianchor = path.i val janchor = path.j val original = - new munit.internal.difflib.Chunk[T]( + new munit.diff.Chunk[T]( ianchor, copyOfRange(orig, ianchor, i) ) val revised = - new munit.internal.difflib.Chunk[T]( + new munit.diff.Chunk[T]( janchor, copyOfRange(rev, janchor, j) ) - val delta: munit.internal.difflib.Delta[T] = + val delta: munit.diff.Delta[T] = if (original.size == 0 && revised.size != 0) { - new munit.internal.difflib.InsertDelta[T](original, revised) + new munit.diff.InsertDelta[T](original, revised) } else if (original.size > 0 && revised.size == 0) { - new munit.internal.difflib.DeleteDelta[T](original, revised) + new munit.diff.DeleteDelta[T](original, revised) } else { - new munit.internal.difflib.ChangeDelta[T](original, revised) + new munit.diff.ChangeDelta[T](original, revised) } patch.addDelta(delta) if (path.isSnake) { diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Patch.scala b/munit-diff/shared/src/main/scala/munit/diff/Patch.scala similarity index 94% rename from munit/shared/src/main/scala/munit/internal/difflib/Patch.scala rename to munit-diff/shared/src/main/scala/munit/diff/Patch.scala index 4b29ecb9..5045f780 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/Patch.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Patch.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff import java.util import java.util.{Collections, Comparator} diff --git a/munit/shared/src/main/scala/munit/internal/difflib/PathNode.scala b/munit-diff/shared/src/main/scala/munit/diff/PathNode.scala similarity index 96% rename from munit/shared/src/main/scala/munit/internal/difflib/PathNode.scala rename to munit-diff/shared/src/main/scala/munit/diff/PathNode.scala index 95b538ff..92d04771 100644 --- a/munit/shared/src/main/scala/munit/internal/difflib/PathNode.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/PathNode.scala @@ -1,4 +1,4 @@ -package munit.internal.difflib +package munit.diff sealed abstract class PathNode(val i: Int, val j: Int, val prev: PathNode) { diff --git a/munit/shared/src/main/scala/munit/Printer.scala b/munit-diff/shared/src/main/scala/munit/diff/Printer.scala similarity index 99% rename from munit/shared/src/main/scala/munit/Printer.scala rename to munit-diff/shared/src/main/scala/munit/diff/Printer.scala index ee94cf53..25395c3f 100644 --- a/munit/shared/src/main/scala/munit/Printer.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/Printer.scala @@ -1,4 +1,4 @@ -package munit +package munit.diff /** * Implement this trait to customize the default printer diff --git a/munit/shared/src/main/scala/munit/internal/console/AnsiColors.scala b/munit-diff/shared/src/main/scala/munit/diff/console/AnsiColors.scala similarity index 97% rename from munit/shared/src/main/scala/munit/internal/console/AnsiColors.scala rename to munit-diff/shared/src/main/scala/munit/diff/console/AnsiColors.scala index 4ad41ed8..f9a15918 100644 --- a/munit/shared/src/main/scala/munit/internal/console/AnsiColors.scala +++ b/munit-diff/shared/src/main/scala/munit/diff/console/AnsiColors.scala @@ -1,4 +1,4 @@ -package munit.internal.console +package munit.diff.console object AnsiColors { val LightRed = "\u001b[91m" diff --git a/munit-diff/shared/src/main/scala/munit/diff/console/Printers.scala b/munit-diff/shared/src/main/scala/munit/diff/console/Printers.scala new file mode 100644 index 00000000..4e9c9ed6 --- /dev/null +++ b/munit-diff/shared/src/main/scala/munit/diff/console/Printers.scala @@ -0,0 +1,59 @@ +package munit.diff.console + +import munit.diff.{EmptyPrinter, Printer} + +import scala.annotation.switch + +object Printers { + + def print(input: String): String = { + val out = new StringBuilder() + printString(input, out, EmptyPrinter) + munit.diff.console.AnsiColors.filterAnsi(out.toString()) + } + + def printString( + string: String, + out: StringBuilder, + printer: Printer + ): Unit = { + val isMultiline = printer.isMultiline(string) + if (isMultiline) { + out.append('"') + out.append('"') + out.append('"') + out.append(string) + out.append('"') + out.append('"') + out.append('"') + } else { + out.append('"') + var i = 0 + while (i < string.length()) { + printChar(string.charAt(i), out) + i += 1 + } + out.append('"') + } + } + + def printChar( + c: Char, + sb: StringBuilder, + isEscapeUnicode: Boolean = true + ): Unit = + (c: @switch) match { + case '"' => sb.append("\\\"") + case '\\' => sb.append("\\\\") + case '\b' => sb.append("\\b") + case '\f' => sb.append("\\f") + case '\n' => sb.append("\\n") + case '\r' => sb.append("\\r") + case '\t' => sb.append("\\t") + case c => + val isNonReadableAscii = c < ' ' || (c > '~' && isEscapeUnicode) + if (isNonReadableAscii && !Character.isLetter(c)) + sb.append("\\u%04x".format(c.toInt)) + else sb.append(c) + } +} diff --git a/munit/js-native/src/main/scala/munit/internal/junitinterface/JUnitReporter.scala b/munit/js-native/src/main/scala/munit/internal/junitinterface/JUnitReporter.scala index 03d03f64..5837607d 100644 --- a/munit/js-native/src/main/scala/munit/internal/junitinterface/JUnitReporter.scala +++ b/munit/js-native/src/main/scala/munit/internal/junitinterface/JUnitReporter.scala @@ -4,7 +4,7 @@ package munit.internal.junitinterface -import munit.internal.console.AnsiColors +import munit.diff.console.AnsiColors import sbt.testing._ import munit.internal.PlatformCompat diff --git a/munit/shared/src/main/scala/munit/Assertions.scala b/munit/shared/src/main/scala/munit/Assertions.scala index 5f05d7a5..54f33aa7 100644 --- a/munit/shared/src/main/scala/munit/Assertions.scala +++ b/munit/shared/src/main/scala/munit/Assertions.scala @@ -1,13 +1,14 @@ package munit -import munit.internal.console.{Lines, Printers, StackTraces} -import munit.internal.difflib.ComparisonFailExceptionHandler -import munit.internal.difflib.Diffs +import munit.internal.console.{Lines, StackTraces} +import munit.internal.console.Printers +import munit.diff.Printer +import munit.diff.EmptyPrinter import scala.reflect.ClassTag import scala.util.control.NonFatal import scala.collection.mutable -import munit.internal.console.AnsiColors +import munit.diff.console.AnsiColors import org.junit.AssumptionViolatedException import munit.internal.MacroCompat @@ -54,7 +55,7 @@ trait Assertions extends MacroCompat.CompileErrorMacro { Diffs.assertNoDiff( obtained, expected, - ComparisonFailExceptionHandler.fromAssertions(this, Clues.empty), + exceptionHandlerFromAssertions(this, Clues.empty), munitPrint(clue), printObtainedAsStripMargin = true ) @@ -291,6 +292,21 @@ trait Assertions extends MacroCompat.CompileErrorMacro { ) } + private def exceptionHandlerFromAssertions( + assertions: Assertions, + clues: => Clues + ): ComparisonFailExceptionHandler = + new ComparisonFailExceptionHandler { + def handle( + message: String, + obtained: String, + expected: String, + loc: Location + ): Nothing = { + assertions.failComparison(message, obtained, expected, clues)(loc) + } + } + private val munitCapturedClues: mutable.ListBuffer[Clue[_]] = mutable.ListBuffer.empty def munitCaptureClues[T](thunk: => T): (T, Clues) = diff --git a/munit/shared/src/main/scala/munit/Clue.scala b/munit/shared/src/main/scala/munit/Clue.scala index cb905bee..028c1aba 100644 --- a/munit/shared/src/main/scala/munit/Clue.scala +++ b/munit/shared/src/main/scala/munit/Clue.scala @@ -12,5 +12,6 @@ class Clue[+T]( object Clue extends MacroCompat.ClueMacro { @deprecated("use fromValue instead", "1.0.0") def empty[T](value: T): Clue[T] = fromValue(value) - def fromValue[T](value: T): Clue[T] = new Clue("", value, "") + def fromValue[T](value: T): Clue[T] = + new Clue("", value, "") } diff --git a/munit/shared/src/main/scala/munit/Compare.scala b/munit/shared/src/main/scala/munit/Compare.scala index 15745d7a..62e6aa95 100644 --- a/munit/shared/src/main/scala/munit/Compare.scala +++ b/munit/shared/src/main/scala/munit/Compare.scala @@ -1,7 +1,5 @@ package munit -import munit.internal.difflib.Diffs -import munit.internal.difflib.ComparisonFailExceptionHandler import scala.annotation.implicitNotFound /** diff --git a/munit/shared/src/main/scala/munit/ComparisonFailExceptionHandler.scala b/munit/shared/src/main/scala/munit/ComparisonFailExceptionHandler.scala new file mode 100644 index 00000000..7f9ba7f6 --- /dev/null +++ b/munit/shared/src/main/scala/munit/ComparisonFailExceptionHandler.scala @@ -0,0 +1,10 @@ +package munit + +trait ComparisonFailExceptionHandler { + def handle( + message: String, + obtained: String, + expected: String, + location: Location + ): Nothing +} diff --git a/munit/shared/src/main/scala/munit/Diffs.scala b/munit/shared/src/main/scala/munit/Diffs.scala new file mode 100644 index 00000000..c0252ac3 --- /dev/null +++ b/munit/shared/src/main/scala/munit/Diffs.scala @@ -0,0 +1,33 @@ +package munit + +import munit.diff.Diff + +object Diffs { + + def assertNoDiff( + obtained: String, + expected: String, + handler: ComparisonFailExceptionHandler, + title: String, + printObtainedAsStripMargin: Boolean + )(implicit loc: Location): Boolean = { + if (obtained.isEmpty && !expected.isEmpty) { + val msg = + s"""|Obtained empty output! + |=> Expected: + |$expected""".stripMargin + handler.handle(msg, obtained, expected, loc) + } + val diff = new Diff(obtained, expected) + if (diff.isEmpty) true + else { + handler.handle( + diff.createReport(title, printObtainedAsStripMargin), + obtained, + expected, + loc + ) + } + } + +} diff --git a/munit/shared/src/main/scala/munit/internal/console/Lines.scala b/munit/shared/src/main/scala/munit/internal/console/Lines.scala index 9f792151..440a55dc 100644 --- a/munit/shared/src/main/scala/munit/internal/console/Lines.scala +++ b/munit/shared/src/main/scala/munit/internal/console/Lines.scala @@ -44,10 +44,16 @@ class Lines extends Serializable { .append(format(location.line - 1)) .append(slice(0)) .append('\n') - .append(AnsiColors.use(AnsiColors.Reversed)) + .append( + munit.diff.console.AnsiColors + .use(munit.diff.console.AnsiColors.Reversed) + ) .append(format(location.line)) .append(slice(1)) - .append(AnsiColors.use(AnsiColors.Reset)) + .append( + munit.diff.console.AnsiColors + .use(munit.diff.console.AnsiColors.Reset) + ) if (slice.length >= 3) out .append('\n') diff --git a/munit/shared/src/main/scala/munit/internal/console/Printers.scala b/munit/shared/src/main/scala/munit/internal/console/Printers.scala index 96a9a196..b952de6c 100644 --- a/munit/shared/src/main/scala/munit/internal/console/Printers.scala +++ b/munit/shared/src/main/scala/munit/internal/console/Printers.scala @@ -1,13 +1,18 @@ // Adaptation of https://github.com/lihaoyi/PPrint/blob/e6a918c259ed7ae1998bbf58c360334a3f0157ca/pprint/src/pprint/Walker.scala package munit.internal.console -import munit.internal.Compat -import munit.{EmptyPrinter, Location, Printable, Printer} - -import scala.annotation.switch +import munit.Location +import munit.diff.Printer +import munit.diff.EmptyPrinter +import munit.diff.console.{Printers => DiffPrinters} import munit.Clues +import munit.Printable +import munit.internal.Compat object Printers { + + import DiffPrinters._ + def log(any: Any, printer: Printer = EmptyPrinter)(implicit loc: Location ): Unit = { @@ -123,7 +128,7 @@ object Printers { } } loop(any, indent = 0) - AnsiColors.filterAnsi(out.toString()) + munit.diff.console.AnsiColors.filterAnsi(out.toString()) } private def printApply[T]( @@ -163,31 +168,6 @@ object Printers { } } - private def printString( - string: String, - out: StringBuilder, - printer: Printer - ): Unit = { - val isMultiline = printer.isMultiline(string) - if (isMultiline) { - out.append('"') - out.append('"') - out.append('"') - out.append(string) - out.append('"') - out.append('"') - out.append('"') - } else { - out.append('"') - var i = 0 - while (i < string.length()) { - printChar(string.charAt(i), out) - i += 1 - } - out.append('"') - } - } - /** * Pretty-prints this string with non-visible characters escaped. * @@ -216,24 +196,4 @@ object Printers { out.toString() } - private def printChar( - c: Char, - sb: StringBuilder, - isEscapeUnicode: Boolean = true - ): Unit = - (c: @switch) match { - case '"' => sb.append("\\\"") - case '\\' => sb.append("\\\\") - case '\b' => sb.append("\\b") - case '\f' => sb.append("\\f") - case '\n' => sb.append("\\n") - case '\r' => sb.append("\\r") - case '\t' => sb.append("\\t") - case c => - val isNonReadableAscii = c < ' ' || (c > '~' && isEscapeUnicode) - if (isNonReadableAscii && !Character.isLetter(c)) - sb.append("\\u%04x".format(c.toInt)) - else sb.append(c) - } - } diff --git a/munit/shared/src/main/scala/munit/internal/difflib/ComparisonFailExceptionHandler.scala b/munit/shared/src/main/scala/munit/internal/difflib/ComparisonFailExceptionHandler.scala deleted file mode 100644 index 5b51cbf8..00000000 --- a/munit/shared/src/main/scala/munit/internal/difflib/ComparisonFailExceptionHandler.scala +++ /dev/null @@ -1,30 +0,0 @@ -package munit.internal.difflib - -import munit.Location -import munit.Assertions -import munit.Clues - -trait ComparisonFailExceptionHandler { - def handle( - message: String, - obtained: String, - expected: String, - location: Location - ): Nothing -} -object ComparisonFailExceptionHandler { - def fromAssertions( - assertions: Assertions, - clues: => Clues - ): ComparisonFailExceptionHandler = - new ComparisonFailExceptionHandler { - def handle( - message: String, - obtained: String, - expected: String, - loc: Location - ): Nothing = { - assertions.failComparison(message, obtained, expected, clues)(loc) - } - } -} diff --git a/munit/shared/src/main/scala/munit/internal/difflib/Diffs.scala b/munit/shared/src/main/scala/munit/internal/difflib/Diffs.scala deleted file mode 100644 index a8c983dd..00000000 --- a/munit/shared/src/main/scala/munit/internal/difflib/Diffs.scala +++ /dev/null @@ -1,80 +0,0 @@ -package munit.internal.difflib - -import munit.Location - -object Diffs { - - def create(obtained: String, expected: String): Diff = - new Diff(obtained, expected) - - @deprecated("") - def assertNoDiff( - obtained: String, - expected: String, - fail: String => Nothing, - title: String = "", - printObtainedAsStripMargin: Boolean = true - )(implicit loc: Location): Boolean = { - assertNoDiff( - obtained, - expected, - new ComparisonFailExceptionHandler { - def handle( - message: String, - obtained: String, - expected: String, - loc: Location - ): Nothing = fail(message) - }, - title, - printObtainedAsStripMargin - ) - } - - def assertNoDiff( - obtained: String, - expected: String, - handler: ComparisonFailExceptionHandler, - title: String, - printObtainedAsStripMargin: Boolean - )(implicit loc: Location): Boolean = { - if (obtained.isEmpty && !expected.isEmpty) { - val msg = - s"""|Obtained empty output! - |=> Expected: - |$expected""".stripMargin - handler.handle(msg, obtained, expected, loc) - } - val diff = new Diff(obtained, expected) - if (diff.isEmpty) true - else { - handler.handle( - diff.createReport(title, printObtainedAsStripMargin), - obtained, - expected, - loc - ) - } - } - - def createDiffOnlyReport( - obtained: String, - expected: String - ): String = { - create(obtained, expected).createDiffOnlyReport() - } - - def createReport( - obtained: String, - expected: String, - title: String, - printObtainedAsStripMargin: Boolean = true - ): String = { - create(obtained, expected).createReport(title, printObtainedAsStripMargin) - } - - def unifiedDiff(obtained: String, expected: String): String = { - create(obtained, expected).unifiedDiff - } - -} diff --git a/project/MimaExclusions.scala b/project/MimaExclusions.scala new file mode 100644 index 00000000..ed97fde3 --- /dev/null +++ b/project/MimaExclusions.scala @@ -0,0 +1,173 @@ +import com.typesafe.tools.mima.core._ + +object MimaExclusions { + + val list = List( + ProblemFilters.exclude[MissingClassProblem]("munit.EmptyPrinter"), + ProblemFilters.exclude[MissingClassProblem]("munit.EmptyPrinter$"), + ProblemFilters.exclude[MissingClassProblem]("munit.Printer"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.console.AnsiColors" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.console.AnsiColors$" + ), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "munit.internal.console.Printers.print$default$2" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.internal.console.Printers.print" + ), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "munit.internal.console.Printers.log$default$2" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.internal.console.Printers.log" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.internal.console.Printers.log" + ), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "munit.internal.console.Printers.log$default$2" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.internal.console.Printers.print" + ), + ProblemFilters.exclude[IncompatibleResultTypeProblem]( + "munit.internal.console.Printers.print$default$2" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.ChangeDelta" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Chunk"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DeleteDelta" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Delta"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Delta$TYPE" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Delta$TYPE$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Delta$TYPE$CHANGE$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Delta$TYPE$DELETE$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Delta$TYPE$INSERT$" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Diff"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DiffAlgorithm" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DiffNode" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DiffUtils" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DiffUtils$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.DifferentiationFailedException" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Diffs"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Diffs$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Equalizer" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.Equalizer$" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.InsertDelta" + ), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.MyersDiff" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Patch"), + ProblemFilters.exclude[MissingClassProblem]( + "munit.internal.difflib.PathNode" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.internal.difflib.Snake"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.MUnitRunner.descriptions" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.MUnitRunner.testNames" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.MUnitRunner.munitTests" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.ValueTransforms.munitTimeout" + ), + ProblemFilters.exclude[MissingTypesProblem]("munit.FailException"), + ProblemFilters.exclude[MissingTypesProblem]("munit.FailSuiteException"), + ProblemFilters.exclude[MissingTypesProblem]( + "munit.TestValues$FlakyFailure" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.internal.junitinterface.JUnitComputer.this" + ), + // Known breaking changes for MUnit v1 + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.Assertions.assertNotEquals" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.Assertions.assertEquals" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.Assertions.assertNotEquals" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.Assertions.assertEquals" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.FunSuite.assertNotEquals" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.FunSuite.assertEquals" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.FunSuite.munitTestTransform" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.GenericAfterEach"), + ProblemFilters.exclude[MissingClassProblem]("munit.GenericBeforeEach"), + ProblemFilters.exclude[MissingClassProblem]("munit.GenericTest"), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.MUnitRunner.createTestDescription" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.Suite.beforeEach" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.Suite.afterEach" + ), + ProblemFilters.exclude[MissingClassProblem]("munit.Suite$Fixture"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.TestTransforms#TestTransform.apply" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.FunFixtures#FunFixture.this" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.SuiteTransforms#SuiteTransform.this" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.TestTransforms#TestTransform.this" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "munit.ValueTransforms#ValueTransform.this" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "munit.ScalaCheckSuite.unitToProp" + ) + ) +} diff --git a/tests/shared/src/main/scala/munit/BaseFrameworkSuite.scala b/tests/shared/src/main/scala/munit/BaseFrameworkSuite.scala index 4393393b..f54e3224 100644 --- a/tests/shared/src/main/scala/munit/BaseFrameworkSuite.scala +++ b/tests/shared/src/main/scala/munit/BaseFrameworkSuite.scala @@ -9,7 +9,7 @@ import java.nio.charset.StandardCharsets import sbt.testing.Logger import scala.util.control.NonFatal import java.util.regex.Pattern -import munit.internal.console.AnsiColors +import munit.diff.console.AnsiColors import munit.internal.PlatformCompat import scala.concurrent.Future diff --git a/tests/shared/src/test/scala/munit/DiffsSuite.scala b/tests/shared/src/test/scala/munit/DiffsSuite.scala index 2e9db478..f8287268 100644 --- a/tests/shared/src/test/scala/munit/DiffsSuite.scala +++ b/tests/shared/src/test/scala/munit/DiffsSuite.scala @@ -1,12 +1,10 @@ package munit -import munit.internal.difflib.Diffs - class DiffsSuite extends FunSuite { self => test("ansi") { - val diff1 = Diffs.unifiedDiff("a", "b") - val diff2 = Diffs.unifiedDiff("a", "c") - val obtained = Diffs.unifiedDiff(diff1, diff2) + val diff1 = munit.diff.Diffs.unifiedDiff("a", "b") + val diff2 = munit.diff.Diffs.unifiedDiff("a", "c") + val obtained = munit.diff.Diffs.unifiedDiff(diff1, diff2) // Asserts that a roundtrip of ANSI color processing still produces // intuitive results. assertNoDiff( @@ -25,7 +23,7 @@ class DiffsSuite extends FunSuite { self => expected: String )(implicit loc: Location): Unit = { test(name) { - val obtained = Diffs.unifiedDiff(a, b) + val obtained = munit.diff.Diffs.unifiedDiff(a, b) assertNoDiff(obtained, expected) } }