Skip to content

Commit

Permalink
Support forced snapshot overwrite
Browse files Browse the repository at this point in the history
  • Loading branch information
keynmol committed Feb 12, 2024
1 parent 03c41a8 commit a3b4074
Show file tree
Hide file tree
Showing 19 changed files with 100 additions and 16 deletions.
7 changes: 7 additions & 0 deletions README.md
Expand Up @@ -61,6 +61,13 @@ SBT settings:
stay sane.

- `snapshotsPackageName` - package name to use for generated file
- `snapshotsForceOverwrite` - if set to `true`, snapshot assertions won't fail the tests,
but instead they will directly overwrite snapshot contents with the new value.

It is particularly useful in a setting where you want to only check snapshots on CI,
but always update them locally without having to explicitly accept.

Recommended configuration is therefore `snapshotsForceOverwrite := !sys.env.contains("CI")` - or whatever way you can detect that your code is running in CI

[Sample MUnit tests](modules/example/src/test/scala/MunitExampleTests.scala)

Expand Down
Expand Up @@ -28,18 +28,17 @@ trait MunitSnapshotsIntegration {
def assertSnapshot(name: String, contents: String) = {
Snapshots.read(name) match {
case None =>
Snapshots.recordChanges(
name,
contents,
Diffs.create(contents, "").createDiffOnlyReport()
)
// If snapshot is not found, we directly write its contents
Snapshots.write(name, contents)

case Some(value) =>
val diff = Diffs.create(contents, value)
if (!diff.isEmpty) {
val diffReport = diff.createDiffOnlyReport()
Snapshots.recordChanges(name, contents, diffReport)
Assertions.assertNoDiff(contents, value)
if (!Snapshots.forceOverwrite) {
val diffReport = diff.createDiffOnlyReport()
Snapshots.recordChanges(name, contents, diffReport)
Assertions.assertNoDiff(contents, value)
} else Snapshots.write(name, contents)
} else
Snapshots.clearChanges(name)
}
Expand Down
11 changes: 7 additions & 4 deletions modules/snapshots-buildtime/src/main/scala/SnapshotsBuild.scala
Expand Up @@ -148,7 +148,8 @@ object SnapshotsBuild {
packageName: String,
snapshotsDestination: File,
sourceDestination: File,
tmpLocation: File
tmpLocation: File,
forceOverwrite: Boolean = true
) = {
Files.createDirectories(sourceDestination.getParentFile().toPath)

Expand All @@ -157,7 +158,8 @@ object SnapshotsBuild {
SnapshotsGenerate(
snapshotsDestination,
tmpLocation,
packageName
packageName,
forceOverwrite
).linesIterator.toList
)

Expand Down Expand Up @@ -195,11 +197,12 @@ object SnapshotsBuild {
private def SnapshotsGenerate(
path: File,
tempPath: File,
packageName: String
packageName: String,
forceOverwrite: Boolean
) =
s"""
|package $packageName
|object Snapshots extends com.indoorvivants.snapshots.Snapshots(location = "$path", tmpLocation = "$tempPath")
|object Snapshots extends com.indoorvivants.snapshots.Snapshots(location = "$path", tmpLocation = "$tempPath", forceOverwrite = $forceOverwrite)
""".trim.stripMargin

}
12 changes: 11 additions & 1 deletion modules/snapshots-runtime/src/main/scala/Snapshots.scala
Expand Up @@ -16,7 +16,11 @@

package com.indoorvivants.snapshots

case class Snapshots(location: String, tmpLocation: String) extends Platform {
case class Snapshots(
location: String,
tmpLocation: String,
forceOverwrite: Boolean
) extends Platform {

private def getTmpFile(name: String) =
tmpLocation.resolve(name)
Expand All @@ -36,6 +40,12 @@ case class Snapshots(location: String, tmpLocation: String) extends Platform {
tmpFileDiff.fileWriteContents(diff)
}

def write(name: String, contents: String): Unit = {
val file = location.resolve(name)
location.createDirectories()
file.fileWriteContents(contents)
}

def clearChanges(name: String): Unit = {
val tmpName = name + "__snap.new"
val tmpDiff = name + "__snap.new.diff"
Expand Down
14 changes: 12 additions & 2 deletions modules/snapshots-sbt-plugin/src/main/scala/SnapshotsPlugin.scala
Expand Up @@ -43,6 +43,12 @@ object SnapshotsPlugin extends AutoPlugin {
"Whether to add snapshot runtime to the build - true by default, you shouldn't need to touch this"
)

val snapshotsForceOverwrite = settingKey[Boolean](
"(default: false) If set to true, tests where there is a snapshot mismatch won't fail, instead overwriting snapshot directly with new contents - meaning you don't have to run snapshotCheck/snapshotsAcceptAll.\n" +
"A recommended workflow with this option is to only enable it if NOT running on CI - e.g. `snapshotsForceOverwrite := !sys.env.contains(\"CI\")`.\n" +
"This way snapshot compliance will be checked on CI, but local workflow will be much quicker with immediate snapshot overwriting"
)

val snapshotsTemporaryDirectory =
settingKey[File]("Temp folder where snapshot diffs will be created")

Expand Down Expand Up @@ -82,7 +88,10 @@ object SnapshotsPlugin extends AutoPlugin {
snapshotsProjectIdentifier := thisProject.value.id,
snapshotsAddRuntimeDependency := true,
snapshotsIntegrations := Seq.empty,
snapshotsTemporaryDirectory := (Test / managedResourceDirectories).value.head / "snapshots-tmp",
snapshotsForceOverwrite := false,
snapshotsTemporaryDirectory := (
Test / managedResourceDirectories
).value.head / "snapshots-tmp",
snapshotsCheck := Def
.task {
SnapshotsBuild.checkSnapshots(
Expand Down Expand Up @@ -122,7 +131,8 @@ object SnapshotsPlugin extends AutoPlugin {
snapshotsDestination =
(Test / resourceDirectory).value / "snapshots" / snapshotsProjectIdentifier.value,
sourceDestination = dest / "Snapshots.scala",
tmpLocation = snapshotsTemporaryDirectory.value
tmpLocation = snapshotsTemporaryDirectory.value,
forceOverwrite = snapshotsForceOverwrite.value
)

val integrations = snapshotsIntegrations.value.flatMap { integ =>
Expand Down
@@ -0,0 +1,29 @@
lazy val root = projectMatrix
.in(file("."))
.jvmPlatform(Seq("3.3.1", "2.13.12"))
.nativePlatform(Seq("3.3.1"))
.jsPlatform(Seq("3.3.1"))
.settings(
snapshotsPackageName := "example.bla",
snapshotsIntegrations += SnapshotIntegration.MUnit,
libraryDependencies +=
"org.scalameta" %%% "munit" % "1.0.0-M7" % Test,
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
snapshotsForceOverwrite := true,
check := {
val dir =
(Test / resourceDirectory).value / "snapshots" / snapshotsProjectIdentifier.value

val contents = IO.read(dir / "my.snapshot")
val expected = "hello - more stuff"

assert(
contents == expected,
s"Snapshot contents didn't match: expected `$expected`, got `$contents`"
)

}
)
.enablePlugins(SnapshotsPlugin)

val check = taskKey[Unit]("")
@@ -0,0 +1,9 @@
$ exists src/test/resources/snapshots/root3/my.snapshot
$ exists src/test/resources/snapshots/rootNative3/my.snapshot
$ exists src/test/resources/snapshots/rootJS3/my.snapshot
> root3/test
> rootNative3/test
> rootJS3/test
> root3/check
> rootNative3/check
> rootJS3/check
Expand Up @@ -8,6 +8,7 @@ lazy val root = projectMatrix
snapshotsIntegrations += SnapshotIntegration.MUnit,
libraryDependencies +=
"org.scalameta" %%% "munit" % "1.0.0-M7" % Test,
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
)
.enablePlugins(SnapshotsPlugin)

@@ -0,0 +1,6 @@
addSbtPlugin(
"com.indoorvivants.snapshots" % "sbt-snapshots" % sys.props("plugin.version")
)
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.14.0")
@@ -0,0 +1,10 @@
package example.bla

import com.indoorvivants.snapshots.munit_integration._
import munit._

class MunitExampleTests extends FunSuite with MunitSnapshotsIntegration {
test("hello") {
assertSnapshot("my.snapshot", "hello - more stuff")
}
}

0 comments on commit a3b4074

Please sign in to comment.