Skip to content

Commit

Permalink
Much cleaner output, and default kotlin indentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsaff committed May 3, 2022
1 parent 4c5f4b6 commit cf28d8d
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 120 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ gradle-app.setting

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
local.properties
.idea
144 changes: 73 additions & 71 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,111 +16,113 @@ limitations under the License.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

buildscript {
repositories {
google()
mavenCentral()
}
repositories {
google()
mavenCentral()
}
}

plugins {
id("java")
id("java-library")
id("org.jetbrains.kotlin.jvm") version "1.6.20"
id("signing")
id("maven-publish")
id("java")
id("java-library")
id("org.jetbrains.kotlin.jvm") version "1.6.20"
id("signing")
id("maven-publish")
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

kotlin {}

dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("stdlib-jdk8"))
testImplementation("junit:junit:4.13.1")
}

repositories {
mavenCentral()
mavenCentral()
}

val sourcesJar by tasks.creating(Jar::class) {
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
archiveClassifier.set("sources")
from(sourceSets.main.get().allSource)
}

val javadocJar by tasks.creating(Jar::class) {
dependsOn.add(tasks["javadoc"])
archiveClassifier.set("javadoc")
from(tasks["javadoc"])
dependsOn.add(tasks["javadoc"])
archiveClassifier.set("javadoc")
from(tasks["javadoc"])
}

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "1.8"
}

publishing {
publications {
create<MavenPublication>("maven") {
groupId = "net.saff.checkmark"
artifactId = "checkmark"
version = "0.1.1"

from(components["java"])


artifact(sourcesJar)
artifact(javadocJar)

pom {
name.set("checkmark")
description.set("Minimum viable kotlin assertion framework")
url.set("https://github.com/dsaff/checkmark")

licenses {
license {
name.set("Apache License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}

developers {
developer {
id.set("dsaff")
name.set("David Saff")
email.set("[email protected]")
}
}

scm {
connection.set("https://github.com/dsaff/checkmark.git")
developerConnection.set("https://github.com/dsaff/checkmark.git")
url.set("https://github.com/dsaff/checkmark")
}
}
publications {
create<MavenPublication>("maven") {
groupId = "net.saff.checkmark"
artifactId = "checkmark"
version = "0.1.2"

from(components["java"])


artifact(sourcesJar)
artifact(javadocJar)

pom {
name.set("checkmark")
description.set("Minimum viable kotlin assertion framework")
url.set("https://github.com/dsaff/checkmark")

licenses {
license {
name.set("Apache License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
}

repositories {
maven {
// For snapshots, url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots")
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2")
credentials {
// Hopefully these are grabbed from gradle.properties a la
// https://community.sonatype.com/t/how-to-hide-the-nexus-username-and-password-when-publish-artifacts-to-nexus/4439/2
username = nexusUsername
password = nexusPassword
}

developers {
developer {
id.set("dsaff")
name.set("David Saff")
email.set("[email protected]")
}
}

scm {
connection.set("https://github.com/dsaff/checkmark.git")
developerConnection.set("https://github.com/dsaff/checkmark.git")
url.set("https://github.com/dsaff/checkmark")
}
}
}
}

val nexusUsername: String by project
val nexusPassword: String by project

repositories {
maven {
// For snapshots, url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots")
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2")
credentials {
username = nexusUsername
password = nexusPassword
}
}
}
}

signing {
sign(publishing.publications["maven"])
sign(publishing.publications["maven"])
}
144 changes: 95 additions & 49 deletions src/main/java/net/saff/checkmark/Checkmark.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,64 +16,110 @@ limitations under the License.
package net.saff.checkmark

class Checkmark {
class Failure(s: String, e: Throwable? = null) : java.lang.RuntimeException(s, e)
class Failure(s: String, e: Throwable? = null) : java.lang.RuntimeException(s, e)

private val marks = mutableListOf<() -> String>()
private val marks = mutableListOf<() -> String>()

fun mark(note: String): String {
marks.add { note }
return note
fun <T> mark(note: T): T {
marks.add { note.toString() }
return note
}

fun mark(fn: () -> String) {
marks.add(fn)
}

private fun marks() = marks.joinToString { "\n mark: ${it()}" }

companion object {
fun fail(s: String, e: Throwable? = null): Nothing = throw Failure(s, e)

fun <T> T.check(eval: Checkmark.(T) -> Boolean): T {
val cm = Checkmark()
val result = try {
cm.eval(this)
} catch (t: Throwable) {
fail("ERROR: ${allDebugOutput(this, cm, eval)}", t)
}
if (!result) {
fail("Failed assertion: ${allDebugOutput(this, cm, eval)}")
}
return this
}

fun mark(fn: () -> String) {
marks.add(fn)
fun checks(fn: () -> Unit) {
try {
fn()
} catch (e: Throwable) {
val data =
extractClosureFields(fn).joinToString("") { it.run { "\n$first: [[$second]]" } }
fail(data, e)
}
}

private fun marks() = marks.joinToString { "\n mark: ${it()}" }

companion object {
fun fail(s: String, e: Throwable? = null): Nothing = throw Failure(s, e)

fun <T> T.check(eval: Checkmark.(T) -> Boolean): T {
val cm = Checkmark()
val result = try {
cm.eval(this)
} catch (t: Throwable) {
fail("ERROR: ${allDebugOutput(cm)}", t)
}
if (!result) {
fail("Failed assertion: ${allDebugOutput(cm)}")
}
return this
private fun extractClosureFields(closure: Any) = buildList {
closure::class.java.declaredFields.forEach { field ->
// SAFF: do we understand why this is needed?
if (field.name != "INSTANCE") {
field.isAccessible = true
add(field.name.removePrefix("\$") to field.get(closure))
}
}
}

fun checks(fn: () -> Unit) {
try {
fn()
} catch (e: Throwable) {
val data =
extractClosureFields(fn).joinToString("") { it.run { "\n$first: [[$second]]" } }
fail(data, e)
}
}
private fun <T> allDebugOutput(
receiver: T,
cm: Checkmark,
eval: Checkmark.(T) -> Boolean
): String {
val reports = buildList {
// SAFF: maybe allDebugOutput shouldn't have a receiver?
add("actual" to receiver)
addAll(extractClosureFields(eval))
addAll(cm.marks.map { "marked" to it() })
}
return if (reports.size == 1) {
reports[0].second.toString().forCleanDisplay()
} else {
// SAFF: DUP above
reports.joinToString("") { "\n${it.makeDebugLine()}" }
}
}

private fun extractClosureFields(closure: Any) = buildList {
closure::class.java.declaredFields.forEach { field ->
field.isAccessible = true
add(field.name to field.get(closure))
}
}
// SAFF: inline?
private fun Pair<String, Any?>.makeDebugLine(): String {
// SAFF: DUP above?
return "- $first: ${second.toString().forCleanDisplay()}"
}

private fun <T> T.allDebugOutput(cm: Checkmark) = "<<$this>>${cm.marks()}"
private fun String.forCleanDisplay(): String {
return if (!contains("\n")) {
this
} else {
// SAFF: DUP on " |"?
"\n |${replace("\n", "\n |")}"
}
}

fun <T> T.checkCompletes(eval: Checkmark.(T) -> Unit): T {
val cm = Checkmark()
try {
cm.eval(this)
} catch (e: Throwable) {
throw Exception("Failed: <<$this>>${cm.marks()}", e)
}
return this
}
fun <T> T.checkCompletes(eval: Checkmark.(T) -> Unit): T {
val cm = Checkmark()
try {
cm.eval(this)
} catch (e: Throwable) {
throw Exception("Failed: <<$this>>${cm.marks()}", e)
}
return this
}
}
}
}

fun thrown(fn: () -> Any?): Throwable? {
try {
fn()
} catch (t: Throwable) {
return t
}
return null
}

fun String.showWhitespace() = replace(" ", "_").replace("\n", "\\\n")
28 changes: 28 additions & 0 deletions src/test/java/test/net/saff/checkmark/CheckmarkTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package test.net.saff.checkmark

import net.saff.checkmark.Checkmark.Companion.check
import net.saff.checkmark.showWhitespace
import net.saff.checkmark.thrown
import org.junit.Test

class CheckmarkTest {
@Test
fun checkIncludesCaptures() {
val n = "Apple sauce"
thrown { "Pear soup".check { it == n } }!!.message.check { it!!.contains("Apple") }
}

// SAFF: are failures printed twice on purpose?
@Test
fun outputWithMark() {
// SAFF: no "this" if there's only this
val expect = """
|Failed assertion:
|- actual: A
|- marked: B
""".trimMargin().showWhitespace()
thrown { "A".check { it == mark("B") } }!!.message!!.showWhitespace().check {
it == expect
}
}
}

0 comments on commit cf28d8d

Please sign in to comment.