Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow promoting selected suites based on filter #47

Merged
merged 6 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/markdown/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ Unlike `circe-golden`, snapshot4s is aimed at broader example-based testing. It
Here are a few tools for other languages:
- The [Jest Javascript testing framework](https://jestjs.io/docs/snapshot-testing) supports snapshot testing.
- [Insta.rs](https://insta.rs/) is a snapshot testing tool for Rust.

## Can I choose to promote snapshots only in selected file?

Yes! Similarly to running only specific test with `testOnly *MySuite*` sbt command, you can use `snapshot4sPromote *MySuite*` to only update the snapshots present in and referenced by `MySuite.scala`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2024 SiriusXM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package snapshot4s

import scala.reflect.macros.blackbox

private[snapshot4s] trait AssertFileSnapshotMacro[R] {

/** Assert that a found value is equal to a previously snapshotted value.
*
* If the assertion fails, the file can be recreated with the found contents using `snapshot4sPromote`.
* @see https://siriusxm.github.io/snapshot4s/file-snapshots
*
* @param found The found value.
* @param snapshotPath Path to file containing the previously snapshotted value. This is relative to the resources/snapshot directory.
* @param eq Compares the found and snapshot values.
* @param resultLike Constructs a framework-specific result.
*/
def assertFileSnapshot(found: String, snapshotPath: String)(implicit
config: SnapshotConfig,
snapshotEq: SnapshotEq[String],
resultLike: ResultLike[String, R]
): R = macro AssertFileSnapshotMacro.Macro.impl[R]
}

private[snapshot4s] object AssertFileSnapshotMacro {

class Macro(val c: blackbox.Context) {
import c.universe.*

def impl[E](
found: Expr[String],
snapshotPath: Expr[String]
)(
config: Expr[SnapshotConfig],
snapshotEq: Expr[SnapshotEq[String]],
resultLike: Expr[ResultLike[String, E]]
): Tree = {
// Scala 2 macro system will place this call in client code so the called method must be public
// `FileSnapshotProxy.createFileSnapshot` is introduced to keep `FileSnapshot` private
q"""_root_.snapshot4s.FileSnapshotProxy.createFileSnapshot($found, $snapshotPath, ${c.enclosingPosition.source.path}, $config, $snapshotEq, $resultLike)"""
}
}

}

object FileSnapshotProxy {

def createFileSnapshot[E](
found: String,
snapshotPath: String,
sourceFile: String,
config: SnapshotConfig,
snapshotEq: SnapshotEq[String],
resultLike: ResultLike[String, E]
): E = FileSnapshot(found, snapshotPath, sourceFile, config, snapshotEq, resultLike)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2024 SiriusXM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package snapshot4s

private[snapshot4s] trait AssertFileSnapshotMacro[R]:

/** Assert that a found value is equal to a previously snapshotted value.
*
* If the assertion fails, the file can be recreated with the found contents using `snapshot4sPromote`.
* @see https://siriusxm.github.io/snapshot4s/file-snapshots
*
* @param found The found value.
* @param snapshotPath Path to file containing the previously snapshotted value. This is relative to the resources/snapshot directory.
* @param eq Compares the found and snapshot values.
* @param resultLike Constructs a framework-specific result.
*/
inline def assertFileSnapshot(found: String, snapshotPath: String)(implicit
config: SnapshotConfig,
eq: SnapshotEq[String],
resultLike: ResultLike[String, R]
): R =
${
AssertFileSnapshotMacro.impl(
'found,
'snapshotPath,
'config,
'eq,
'resultLike
)
}

import scala.quoted.*

private[snapshot4s] object AssertFileSnapshotMacro:

def impl[A, E](
found: Expr[String],
snapshotPath: Expr[String],
config: Expr[SnapshotConfig],
snapshotEq: Expr[SnapshotEq[String]],
resultLike: Expr[ResultLike[String, E]]
)(using q: Quotes, ta: Type[A], te: Type[E]): Expr[E] =
import q.reflect.*
val sourceFile = Expr(Position.ofMacroExpansion.sourceFile.path)
'{
FileSnapshot($found, $snapshotPath, $sourceFile, $config, $snapshotEq, $resultLike)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ private[snapshot4s] object FileSnapshot {
def apply[E](
found: String,
snapshotPath: String,
sourceFile: String,
config: SnapshotConfig,
eq: SnapshotEq[String],
resultLike: ResultLike[String, E]
): E = resultLike { () =>
val relativePath = Locations.relativeSourceFilePath(sourceFile, config)
val absoluteSnapshotPath = config.resourceDirectory / RelPath(snapshotPath)
def writePatchFile() = {
val patchPath = config.outputDirectory / RelPath("resource-patch") / RelPath(snapshotPath)
val patchPath =
config.outputDirectory / RelPath("resource-patch") / relativePath / RelPath(snapshotPath)
patchPath.write(found)
}
if (absoluteSnapshotPath.exists()) {
Expand All @@ -43,4 +46,5 @@ private[snapshot4s] object FileSnapshot {
Result.NonExistent(found)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ object InlineSnapshot {
val sourceFileHash = Hashing.calculateHash(sourceFileContent)
val hashHeader = Hashing.produceHashHeader(sourceFileHash)
val changeFile =
config.outputDirectory / RelPath("inline-patch") / relativeSourceFilePath(
config.outputDirectory / RelPath("inline-patch") / Locations.relativeSourceFilePath(
sourceFile,
config
) / RelPath(s"$startPosition-$endPosition")
Expand All @@ -84,17 +84,6 @@ object InlineSnapshot {
changeFile.write(actualStr)
}

private[snapshot4s] def relativeSourceFilePath(
sourceFile: String,
config: SnapshotConfig
): RelPath = {
val baseDirectory = config.sourceDirectory
val sourceFilePath = Path(sourceFile)
sourceFilePath.relativeTo(baseDirectory).getOrElse {
throw new SnapshotConfigUnsupportedError(config)
}
}

// See the Scala 2.13 compiler for the source of the warning we're ignoring:
// https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L118
private final val InterpolatorCodeRegex = """\$\{\s*(.*?)\s*\}""".r
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2024 SiriusXM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package snapshot4s

private[snapshot4s] object Locations {

private[snapshot4s] def relativeSourceFilePath(
sourceFile: String,
config: SnapshotConfig
): RelPath = {
val baseDirectory = config.sourceDirectory
val sourceFilePath = Path(sourceFile)
sourceFilePath.relativeTo(baseDirectory).getOrElse {
throw new SnapshotConfigUnsupportedError(config)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,4 @@ package snapshot4s
*
* @tparam R Assertion result type specific to the test framework. For example, weaver's result type is `IO[Expectations]`.
*/
trait SnapshotAssertions[R] extends AssertInlineSnapshotMacro[R] {

/** Assert that a found value is equal to a previously snapshotted value.
*
* If the assertion fails, the file can be recreated with the found contents using `snapshot4sPromote`.
* @see https://siriusxm.github.io/snapshot4s/file-snapshots
*
* @param found The found value.
* @param snapshotPath Path to file containing the previously snapshotted value. This is relative to the resources/snapshot directory.
* @param eq Compares the found and snapshot values.
* @param resultLike Constructs a framework-specific result.
*/
def assertFileSnapshot(found: String, snapshotPath: String)(implicit
config: SnapshotConfig,
eq: SnapshotEq[String],
resultLike: ResultLike[String, R]
): R = FileSnapshot(found, snapshotPath, config, eq, resultLike)

}
trait SnapshotAssertions[R] extends AssertInlineSnapshotMacro[R] with AssertFileSnapshotMacro[R]
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package snapshot4s
import cats.effect.IO
import weaver.*

object InlineSnapshotSpec extends SimpleIOSuite {
object LocationsSpec extends SimpleIOSuite {

pureTest("calculates relative paths correctly") {
val config = new SnapshotConfig(
Expand All @@ -28,7 +28,7 @@ object InlineSnapshotSpec extends SimpleIOSuite {
sourceDirectory = Path("/path/to/sources")
)
val relativePath =
InlineSnapshot.relativeSourceFilePath("/path/to/sources/TestFile.scala", config)
Locations.relativeSourceFilePath("/path/to/sources/TestFile.scala", config)
expect.eql(relativePath.value, "TestFile.scala")
}

Expand All @@ -39,7 +39,7 @@ object InlineSnapshotSpec extends SimpleIOSuite {
sourceDirectory = Path("/wrong/path/to/sources")
)
val result =
IO(InlineSnapshot.relativeSourceFilePath("/path/to/sources/TestFile.scala", config))
IO(Locations.relativeSourceFilePath("/path/to/sources/TestFile.scala", config))
val message =
"""Your project setup is not supported by snapshot4s. We encourage you to raise an issue at https://github.com/siriusxm/snapshot4s/issues/new?template=bug.md

Expand All @@ -55,4 +55,5 @@ We have detected the following configuration:
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ object FileSnapshotSpec extends SimpleIOSuite {
}

private def assert(found: String, path: PathChunk)(config: SnapshotConfig) = {
FileSnapshot(found, path.toString, config, comparison, resultLike)
FileSnapshot(
found,
path.toString,
s"${config.sourceDirectory.value}/src/test/scala/FileSnapshotSpec.scala",
config,
comparison,
resultLike
)
}

private def writeSnapshot(snapshot: String, path: PathChunk)(config: SnapshotConfig) = {
Expand Down Expand Up @@ -94,7 +101,9 @@ object FileSnapshotSpec extends SimpleIOSuite {
patches <- getPatches(config)
} yield expect.same(
patches,
List(config.outputDirectory.osPath / "resource-patch" / "snapshot")
List(
config.outputDirectory.osPath / "resource-patch" / "src" / "test" / "scala" / "FileSnapshotSpec.scala" / "snapshot"
)
)
}

Expand All @@ -105,7 +114,9 @@ object FileSnapshotSpec extends SimpleIOSuite {
patches <- getPatches(config)
} yield expect.same(
patches,
List(config.outputDirectory.osPath / "resource-patch" / "nested" / "snapshot")
List(
config.outputDirectory.osPath / "resource-patch" / "src" / "test" / "scala" / "FileSnapshotSpec.scala" / "nested" / "snapshot"
)
)
}
}
Loading