diff --git a/README.md b/README.md index ed8d96aca7..3e426a8d41 100644 --- a/README.md +++ b/README.md @@ -170,12 +170,13 @@ We start off with a simple example: ```kotlin -import ch.tutteli.atrium.logic._logic +import ch.tutteli.atrium.api.fluent.en_GB.* +import ch.tutteli.atrium.api.verbs.expect val x = 10 expect(x).toEqual(9) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FirstExampleSpec.kt#L31)[Output](#ex-first) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FirstExample.kt#L32)[Output](#ex-first) ```text I expected subject: 10 (kotlin.Int <1234789>) @@ -207,7 +208,7 @@ The next section shows how you can define multiple expectations for the same sub // two single expectations, only first evaluated expect(4 + 6).toBeLessThan(5).toBeGreaterThan(10) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L24)[Output](#ex-single) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L26)[Output](#ex-single) ```text I expected subject: 10 (kotlin.Int <1234789>) @@ -245,7 +246,7 @@ expect(4 + 6) { toBeGreaterThan(10) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L40)[Output](#ex-group) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L44)[Output](#ex-group) ```text I expected subject: 10 (kotlin.Int <1234789>) @@ -342,19 +343,22 @@ expect { throw IllegalArgumentException("name is empty") }.toThrow() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L64)[Output](#ex-toThrow1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ToThrowExamples.kt#L26)[Output](#ex-toThrow1) ```text -I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpec$1$5$1 <1234789>) +I expected subject: () -> kotlin.Nothing (readme.examples.ToThrowExamples$ex-toThrow1$1 <1234789>) ◆ ▶ thrown exception when called: java.lang.IllegalArgumentException ◾ to be an instance of type: IllegalStateException (java.lang.IllegalStateException) ℹ Properties of the unexpected IllegalArgumentException » message: "name is empty" <1234789> » stacktrace: - ⚬ readme.examples.MostExamplesSpec$1$5$1.invoke(MostExamplesSpec.kt:67) - ⚬ readme.examples.MostExamplesSpec$1$5$1.invoke(MostExamplesSpec.kt:65) - ⚬ readme.examples.MostExamplesSpec$1$5.invoke(MostExamplesSpec.kt:280) - ⚬ readme.examples.MostExamplesSpec$1$5.invoke(MostExamplesSpec.kt:64) + ⚬ readme.examples.ToThrowExamples$ex-toThrow1$1.invoke(ToThrowExamples.kt:29) + ⚬ readme.examples.ToThrowExamples$ex-toThrow1$1.invoke(ToThrowExamples.kt:27) + ⚬ readme.examples.ToThrowExamples.ex-toThrow1(ToThrowExamples.kt:58) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + ⚬ java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + ⚬ java.base/java.lang.reflect.Method.invoke(Method.java:566) ``` @@ -381,10 +385,10 @@ expect { message { toStartWith("firstName") } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L71)[Output](#ex-toThrow2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ToThrowExamples.kt#L34)[Output](#ex-toThrow2) ```text -I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpec$1$6$1 <1234789>) +I expected subject: () -> kotlin.Nothing (readme.examples.ToThrowExamples$ex-toThrow2$1 <1234789>) ◆ ▶ thrown exception when called: java.lang.IllegalArgumentException ◾ ▶ message: null ◾ not to equal: null but to be an instance of: String (kotlin.String) -- Class: java.lang.String @@ -402,10 +406,10 @@ expect { throw IllegalArgumentException() }.toThrow().message.toStartWith("firstName") ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L79)[Output](#ex-toThrow3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ToThrowExamples.kt#L43)[Output](#ex-toThrow3) ```text -I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpec$1$7$1 <1234789>) +I expected subject: () -> kotlin.Nothing (readme.examples.ToThrowExamples$ex-toThrow3$1 <1234789>) ◆ ▶ thrown exception when called: java.lang.IllegalArgumentException ◾ ▶ message: null ◾ not to equal: null but to be an instance of: String (kotlin.String) -- Class: java.lang.String @@ -425,22 +429,25 @@ expect { throw IllegalArgumentException("name is empty", RuntimeException("a cause")) }.notToThrow() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L85)[Output](#ex-notToThrow) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ToThrowExamples.kt#L50)[Output](#ex-notToThrow) ```text -I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpec$1$8$1 <1234789>) +I expected subject: () -> kotlin.Nothing (readme.examples.ToThrowExamples$ex-notToThrow$1 <1234789>) ◆ ▶ invoke(): ❗❗ threw java.lang.IllegalArgumentException ℹ Properties of the unexpected IllegalArgumentException » message: "name is empty" <1234789> » stacktrace: - ⚬ readme.examples.MostExamplesSpec$1$8$1.invoke(MostExamplesSpec.kt:88) - ⚬ readme.examples.MostExamplesSpec$1$8$1.invoke(MostExamplesSpec.kt:86) - ⚬ readme.examples.MostExamplesSpec$1$8.invoke(MostExamplesSpec.kt:89) - ⚬ readme.examples.MostExamplesSpec$1$8.invoke(MostExamplesSpec.kt:85) + ⚬ readme.examples.ToThrowExamples$ex-notToThrow$1.invoke(ToThrowExamples.kt:53) + ⚬ readme.examples.ToThrowExamples$ex-notToThrow$1.invoke(ToThrowExamples.kt:51) + ⚬ readme.examples.ToThrowExamples.ex-notToThrow(ToThrowExamples.kt:54) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + ⚬ java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + ⚬ java.base/java.lang.reflect.Method.invoke(Method.java:566) » cause: java.lang.RuntimeException » message: "a cause" <1234789> » stacktrace: - ⚬ readme.examples.MostExamplesSpec$1$8$1.invoke(MostExamplesSpec.kt:88) + ⚬ readme.examples.ToThrowExamples$ex-notToThrow$1.invoke(ToThrowExamples.kt:53) ``` @@ -493,11 +500,11 @@ expect(myPerson) .its { fullName() } // not evaluated anymore, subject String afterwards .toStartWith("rob") // not evaluated anymore ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L41)[Output](#ex-its-single) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L44)[Output](#ex-its-single) ```text -I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorSpec$1$Person <1234789>) -◆ ▶ its.definedIn(FeatureExtractorSpec.kt:43): false +I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorExamples.Person <1234789>) +◆ ▶ its.definedIn(FeatureExtractorExamples.kt:46): false ◾ to equal: true ``` @@ -538,14 +545,14 @@ Feature extractors follow the common pattern of having two overloads: its { lastName }.toEqual("Dummy") } ``` - ↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L49)[Output](#ex-its-group) + ↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L53)[Output](#ex-its-group) ```text - I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorSpec$1$Person <1234789>) - ◆ ▶ its.definedIn(FeatureExtractorSpec.kt:52): "Robert" <1234789> + I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorExamples.Person <1234789>) + ◆ ▶ its.definedIn(FeatureExtractorExamples.kt:56): "Robert" <1234789> ◾ to start with: "Pe" <1234789> ◾ to end with: "er" <1234789> - ◆ ▶ its.definedIn(FeatureExtractorSpec.kt:58): "Stoll" <1234789> + ◆ ▶ its.definedIn(FeatureExtractorExamples.kt:62): "Stoll" <1234789> ◾ to equal: "Dummy" <1234789> ``` @@ -566,10 +573,10 @@ expect(myPerson) .feature { f(it::fullName) } // not evaluated anymore, subject String afterwards .toStartWith("rob") // not evaluated anymore ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L63)[Output](#ex-property-methods-single) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L68)[Output](#ex-property-methods-single) ```text -I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorSpec$1$Person <1234789>) +I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorExamples.Person <1234789>) ◆ ▶ isStudent: false ◾ to equal: true ``` @@ -609,10 +616,10 @@ expect(myPerson) { // forms an expectation-group feature { f(it::lastName) }.toEqual("Dummy") } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L71)[Output](#ex-property-methods-group) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L77)[Output](#ex-property-methods-group) ```text -I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorSpec$1$Person <1234789>) +I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorExamples.Person <1234789>) ◆ ▶ firstName: "Robert" <1234789> ◾ to start with: "Pe" <1234789> ◾ to end with: "er" <1234789> @@ -658,10 +665,10 @@ expect(myPerson) .toEqual("Robert aka. Stoll") // fails .toStartWith("llotS") // not evaluated anymore ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L85)[Output](#ex-methods-args) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L92)[Output](#ex-methods-args) ```text -I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorSpec$1$Person <1234789>) +I expected subject: Person(firstName=Robert, lastName=Stoll, isStudent=false) (readme.examples.FeatureExtractorExamples.Person <1234789>) ◆ ▶ nickname(false): "Mr. Robert" <1234789> ◾ to equal: "Robert aka. Stoll" <1234789> ``` @@ -700,10 +707,10 @@ expect(myFamily) .feature("the first member's name") { members.first().name } // subject narrowed to String .toEqual("Peter") ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L102)[Output](#ex-arbitrary-features) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L110)[Output](#ex-arbitrary-features) ```text -I expected subject: Family(members=[FamilyMember(name=Robert)]) (readme.examples.FeatureExtractorSpec$1$Family <1234789>) +I expected subject: Family(members=[FamilyMember(name=Robert)]) (readme.examples.FeatureExtractorExamples.Family <1234789>) ◆ ▶ the first member's name: "Robert" <1234789> ◾ to equal: "Peter" <1234789> ``` @@ -748,7 +755,7 @@ expect(listOf(1 to "a", 2 to "b")).get(10) { firstToBe(1) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt#L118)[Output](#ex-within-expectation-functions) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorExamples.kt#L127)[Output](#ex-within-expectation-functions) ```text I expected subject: [(1, a), (2, b)] (java.util.Arrays.ArrayList <1234789>) @@ -868,7 +875,7 @@ expect(x).toBeAnInstanceOf { feature { f(it::flag) }.toEqual(false) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L92)[Output](#ex-type-expectations-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L70)[Output](#ex-type-expectations-1) ```text I expected subject: SubType2(word=hello, flag=true) (readme.examples.SubType2 <1234789>) @@ -892,7 +899,7 @@ expect(x).toBeAnInstanceOf() .feature { f(it::number) } .toEqual(2) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L99)[Output](#ex-type-expectations-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L78)[Output](#ex-type-expectations-2) ```text I expected subject: SubType2(word=hello, flag=true) (readme.examples.SubType2 <1234789>) @@ -922,7 +929,7 @@ Let us look at the case where the subject of the expectation has a [nullable typ val slogan1: String? = "postulating expectations made easy" expect(slogan1).toEqual(null) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L106)[Output](#ex-nullable-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L85)[Output](#ex-nullable-1) ```text I expected subject: "postulating expectations made easy" <1234789> @@ -936,7 +943,7 @@ I expected subject: "postulating expectations made easy" <1234789> val slogan2: String? = null expect(slogan2).toEqual("postulating expectations made easy") ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L110)[Output](#ex-nullable-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L90)[Output](#ex-nullable-2) ```text I expected subject: null @@ -957,7 +964,7 @@ expect(slogan2) // subject has type String? .notToEqualNull() // subject is narrowed to String .toStartWith("atrium") ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L115)[Output](#ex-nullable-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L96)[Output](#ex-nullable-3) ```text I expected subject: null @@ -974,7 +981,7 @@ one without (example above) and one with `assertionCreator`-lambda (example belo ```kotlin expect(slogan2).notToEqualNull { toStartWith("atrium") } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L120)[Output](#ex-nullable-4) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L102)[Output](#ex-nullable-4) ```text I expected subject: null @@ -1016,7 +1023,7 @@ for further examples. ```kotlin expect(listOf(1, 2, 2, 4)).toContain(2, 3) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L124)[Output](#ex-collection-short-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L24)[Output](#ex-collection-short-1) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1054,7 +1061,7 @@ expect(listOf(1, 2, 2, 4)).toContain( { toBeGreaterThan(2).toBeLessThan(4) } ) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L128)[Output](#ex-collection-short-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L29)[Output](#ex-collection-short-2) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1096,7 +1103,7 @@ expect(listOf(1, 2, 3, 4)).toHaveElementsAndAny { toBeLessThan(0) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L135)[Output](#ex-collection-any) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L37)[Output](#ex-collection-any) ```text I expected subject: [1, 2, 3, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1114,7 +1121,7 @@ expect(listOf(1, 2, 3, 4)).toHaveElementsAndNone { toBeGreaterThan(2) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L140)[Output](#ex-collection-none) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L44)[Output](#ex-collection-none) ```text I expected subject: [1, 2, 3, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1134,7 +1141,7 @@ expect(listOf(1, 2, 3, 4)).toHaveElementsAndAll { toBeGreaterThan(2) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L145)[Output](#ex-collection-all) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L51)[Output](#ex-collection-all) ```text I expected subject: [1, 2, 3, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1164,7 +1171,7 @@ Following on the last section we will start with an `inOrder` example: ```kotlin expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.entries({ toBeLessThan(3) }, { toBeLessThan(2) }) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L151)[Output](#ex-collection-builder-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L58)[Output](#ex-collection-builder-1) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1210,7 +1217,7 @@ expect(listOf(1, 2, 2, 4)).toContainExactly( report = { showOnlyFailingIfMoreExpectedElementsThan(2) } ) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L154)[Output](#ex-collection-reportOptions-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L63)[Output](#ex-collection-reportOptions-1) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1241,7 +1248,7 @@ and we happily answer your question there. ```kotlin expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.values(1, 2, 2, 3, 4) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L162)[Output](#ex-collection-builder-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L73)[Output](#ex-collection-builder-2) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1266,7 +1273,7 @@ I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) ```kotlin expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.atLeast(1).butAtMost(2).entries({ toBeLessThan(3) }) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L165)[Output](#ex-collection-builder-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L78)[Output](#ex-collection-builder-3) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1283,7 +1290,7 @@ I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) ```kotlin expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(1, 2, 3, 4) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L168)[Output](#ex-collection-builder-4) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L83)[Output](#ex-collection-builder-4) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1302,7 +1309,7 @@ I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) ```kotlin expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(4, 3, 2, 2, 1) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L171)[Output](#ex-collection-builder-5) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/CollectionExamples.kt#L88)[Output](#ex-collection-builder-5) ```text I expected subject: [1, 2, 2, 4] (java.util.Arrays.ArrayList <1234789>) @@ -1336,7 +1343,7 @@ for further examples. ```kotlin expect(mapOf("a" to 1, "b" to 2)).toContain("c" to 2, "a" to 1, "b" to 1) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L175)[Output](#ex-map-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L26)[Output](#ex-map-1) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1360,7 +1367,7 @@ expect(mapOf("a" to 1, "b" to 2)).toContain( KeyValue("b") { toBeLessThan(2) } ) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L178)[Output](#ex-map-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L31)[Output](#ex-map-2) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1382,7 +1389,7 @@ Again both overloads are provided, one for key-value `Pair`s: ```kotlin expect(mapOf("a" to 1, "b" to 2)).toContainOnly("b" to 2) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L186)[Output](#ex-map-only-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L40)[Output](#ex-map-only-1) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1407,7 +1414,7 @@ expect(mapOf("a" to 1, "b" to 2)).toContainOnly( KeyValue("b") { toBeLessThan(2) } ) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L189)[Output](#ex-map-only-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L45)[Output](#ex-map-only-2) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1435,7 +1442,7 @@ again provide two overloads, one expecting key-value `Pair`s: ```kotlin expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries("b" to 2, "a" to 1) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L197)[Output](#ex-map-builder-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L54)[Output](#ex-map-builder-1) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1462,7 +1469,7 @@ expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries( KeyValue("a") { toBeLessThan(2) }, KeyValue("b") { toBeLessThan(2) }) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L200)[Output](#ex-map-builder-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L59)[Output](#ex-map-builder-2) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1499,11 +1506,11 @@ expect(mapOf("bernstein" to bernstein)) feature { f(it::firstName) }.toEqual("Albert") } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L210)[Output](#ex-map-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L70)[Output](#ex-map-3) ```text I expected subject: {bernstein=Person(firstName=Leonard, lastName=Bernstein, age=50)} (java.util.Collections.SingletonMap <1234789>) -◆ ▶ get("bernstein"): Person(firstName=Leonard, lastName=Bernstein, age=50) (readme.examples.MostExamplesSpec$1$Person <1234789>) +◆ ▶ get("bernstein"): Person(firstName=Leonard, lastName=Bernstein, age=50) (readme.examples.MapExamples.Person <1234789>) ◾ ▶ age: 50 (kotlin.Int <1234789>) ◾ to equal: 60 (kotlin.Int <1234789>) ``` @@ -1519,7 +1526,7 @@ expect(mapOf("a" to 1, "b" to 2)) { values { toHaveElementsAndNone { toBeGreaterThan(1) } } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L222)[Output](#ex-map-4) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L84)[Output](#ex-map-4) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1555,7 +1562,7 @@ expect(linkedMapOf("a" to 1, "b" to 2)).asEntries().toContain.inOrder.only.entri } ) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L228)[Output](#ex-map-5) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MapExamples.kt#L92)[Output](#ex-map-5) ```text I expected subject: {a=1, b=2} (java.util.LinkedHashMap <1234789>) @@ -1593,7 +1600,7 @@ For example, `toExist` will explain which entry was the first one missing: ```kotlin expect(Paths.get("/usr/bin/noprogram")).toExist() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt#L37)[Output](#ex-path-exists) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathExamples.kt#L43)[Output](#ex-path-exists) ```text I expected subject: /usr/bin/noprogram (sun.nio.fs.UnixPath <1234789>) @@ -1609,7 +1616,7 @@ Atrium will give details about why something cannot be accessed, for example whe ```kotlin expect(Paths.get("/root/.ssh/config")).toBeWritable() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt#L41)[Output](#ex-path-writable) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathExamples.kt#L48)[Output](#ex-path-writable) ```text I expected subject: /root/.ssh/config (sun.nio.fs.UnixPath <1234789>) @@ -1626,13 +1633,13 @@ Even in more complicated scenarios, Atrium explains step by step what happened: ```kotlin -val directory = Files.createDirectory(tmpdir.resolve("atrium-path")) +val directory = Files.createDirectory(tmpDir) val file = Files.createFile(directory.resolve("file")) val filePointer = Files.createSymbolicLink(directory.resolve("directory"), file) expect(filePointer.resolve("subfolder/file")).toBeARegularFile() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt#L46)[Output](#ex-path-symlink-and-parent-not-folder) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathExamples.kt#L53)[Output](#ex-path-symlink-and-parent-not-folder) ```text I expected subject: /tmp/atrium-path/directory/subfolder/file (sun.nio.fs.UnixPath <1234789>) @@ -1656,7 +1663,7 @@ expect("filename?") notToContain("?") } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L238)[Output](#ex-because-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L107)[Output](#ex-because-1) ```text I expected subject: "filename?" <1234789> @@ -1717,7 +1724,7 @@ expect(listOf(1, 2, 3, -1)).toHaveElementsAndAll { } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt#L33)[Output](#ex-third-party-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartyExamples.kt#L35)[Output](#ex-third-party-1) ```text I expected subject: [1, 2, 3, -1] (java.util.Arrays.ArrayList <1234789>) @@ -1750,7 +1757,7 @@ fun Expect.notToBeNegative() = expect(-10).notToBeNegative() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt#L42)[Output](#ex-third-party-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartyExamples.kt#L45)[Output](#ex-third-party-2) ```text I expected subject: -10 (kotlin.Int <1234789>) @@ -1763,11 +1770,14 @@ to be greater than or equal to: 0 " <1234789> » stacktrace: - ⚬ readme.examples.ThirdPartySpec$1$2$notToBeNegative$1.invoke(ThirdPartySpec.kt:46) - ⚬ readme.examples.ThirdPartySpec$1$2$notToBeNegative$1.invoke(ThirdPartySpec.kt:44) - ⚬ readme.examples.ThirdPartySpec$1$2.invoke$notToBeNegative(ThirdPartySpec.kt:44) - ⚬ readme.examples.ThirdPartySpec$1$2.invoke(ThirdPartySpec.kt:56) - ⚬ readme.examples.ThirdPartySpec$1$2.invoke(ThirdPartySpec.kt:42) + ⚬ readme.examples.ThirdPartyExamples$ex-third-party-2$notToBeNegative$1.invoke(ThirdPartyExamples.kt:49) + ⚬ readme.examples.ThirdPartyExamples$ex-third-party-2$notToBeNegative$1.invoke(ThirdPartyExamples.kt:47) + ⚬ readme.examples.ThirdPartyExamples.ex_third_party_2$notToBeNegative(ThirdPartyExamples.kt:47) + ⚬ readme.examples.ThirdPartyExamples.ex-third-party-2(ThirdPartyExamples.kt:59) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + ⚬ java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + ⚬ java.base/java.lang.reflect.Method.invoke(Method.java:566) ``` @@ -1795,7 +1805,7 @@ fun Expect.notToBeNegative() = expect(-10).notToBeNegative() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt#L59)[Output](#ex-third-party-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartyExamples.kt#L63)[Output](#ex-third-party-3) ```text I expected subject: -10 (kotlin.Int <1234789>) @@ -1835,7 +1845,7 @@ expectGrouped { } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt#L29)[Output](#ex-data-driven-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenExamples.kt#L31)[Output](#ex-data-driven-1) ```text my expectations: @@ -1878,7 +1888,7 @@ expectGrouped { } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt#L45)[Output](#ex-data-driven-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenExamples.kt#L48)[Output](#ex-data-driven-2) ```text my expectations: @@ -1923,7 +1933,7 @@ expectGrouped { } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt#L82)[Output](#ex-data-driven-nesting) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenExamples.kt#L87)[Output](#ex-data-driven-nesting) ```text my expectations: @@ -1970,7 +1980,7 @@ expectGrouped { } } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt#L63)[Output](#ex-data-driven-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenExamples.kt#L67)[Output](#ex-data-driven-3) ```text my expectations: @@ -2089,10 +2099,10 @@ expect { } }.toThrow { messageToContain("no no no") } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L251)[Output](#ex-add-info-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L123)[Output](#ex-add-info-3) ```text -I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpec$1$38$1 <1234789>) +I expected subject: () -> kotlin.Nothing (readme.examples.MostExamples$ex-add-info-3$1 <1234789>) ◆ ▶ thrown exception when called: java.lang.IllegalArgumentException ◾ to be an instance of type: IllegalStateException (java.lang.IllegalStateException) » ▶ message: @@ -2103,14 +2113,17 @@ I expected subject: () -> kotlin.Nothing (readme.examples.MostExamplesSpe ℹ Properties of the unexpected IllegalArgumentException » message: "no no no..." <1234789> » stacktrace: - ⚬ readme.examples.MostExamplesSpec$1$38$1.invoke(MostExamplesSpec.kt:256) - ⚬ readme.examples.MostExamplesSpec$1$38$1.invoke(MostExamplesSpec.kt:252) - ⚬ readme.examples.MostExamplesSpec$1$38.invoke(MostExamplesSpec.kt:280) - ⚬ readme.examples.MostExamplesSpec$1$38.invoke(MostExamplesSpec.kt:251) + ⚬ readme.examples.MostExamples$ex-add-info-3$1.invoke(MostExamples.kt:128) + ⚬ readme.examples.MostExamples$ex-add-info-3$1.invoke(MostExamples.kt:124) + ⚬ readme.examples.MostExamples.ex-add-info-3(MostExamples.kt:159) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + ⚬ java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + ⚬ java.base/java.lang.reflect.Method.invoke(Method.java:566) » cause: java.lang.UnsupportedOperationException » message: "not supported" <1234789> » stacktrace: - ⚬ readme.examples.MostExamplesSpec$1$38$1.invoke(MostExamplesSpec.kt:254) + ⚬ readme.examples.MostExamples$ex-add-info-3$1.invoke(MostExamples.kt:126) ``` @@ -2131,7 +2144,7 @@ then Atrium reminds us of the possible pitfall. For instance: ```kotlin expect(BigDecimal.TEN).toEqualIncludingScale(BigDecimal("10.0")) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L261)[Output](#ex-pitfall-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L134)[Output](#ex-pitfall-1) ```text I expected subject: 10 (java.math.BigDecimal <1234789>) @@ -2149,7 +2162,7 @@ For instance: ```kotlin expect(listOf(1)).get(0) {} ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt#L264)[Output](#ex-pitfall-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamples.kt#L138)[Output](#ex-pitfall-2) ```text I expected subject: [1] (java.util.Collections.SingletonList <1234789>) @@ -2212,7 +2225,7 @@ and its usage: ```kotlin expect(12).toBeAMultipleOf(5) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt#L44)[Output](#ex-own-boolean-1) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctions.kt#L45)[Output](#ex-own-boolean-1) ```text I expected subject: 12 (kotlin.Int <1234789>) @@ -2260,7 +2273,7 @@ Its usage looks then as follows: ```kotlin expect(13).toBeEven() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt#L57)[Output](#ex-own-boolean-2) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctions.kt#L60)[Output](#ex-own-boolean-2) ```text I expected subject: 13 (kotlin.Int <1234789>) @@ -2289,21 +2302,23 @@ fun Expect.toComplyValidation() = expect(MyDomainModel(alpha1 = 1204)).toComplyValidation() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt#L85)[Output](#ex-third-party-10) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartyExamples.kt#L90)[Output](#ex-third-party-10) ```text -I expected subject: MyDomainModel(alpha1=1204) (readme.examples.ThirdPartySpec$1$MyDomainModel <1234789>) +I expected subject: MyDomainModel(alpha1=1204) (readme.examples.ThirdPartyExamples.MyDomainModel <1234789>) ◆ to comply: validation ℹ Properties of the unexpected IllegalStateException » message: "threshold value for alpha1 exceeded, expected <= 1000, was 1204" <1234789> » stacktrace: - ⚬ readme.examples.ThirdPartySpec$1.invoke$validateMaxThreshold(ThirdPartySpec.kt:82) - ⚬ readme.examples.ThirdPartySpec$1.access$invoke$validateMaxThreshold(ThirdPartySpec.kt:31) - ⚬ readme.examples.ThirdPartySpec$1$4$toComplyValidation$1.invoke(ThirdPartySpec.kt:89) - ⚬ readme.examples.ThirdPartySpec$1$4$toComplyValidation$1.invoke(ThirdPartySpec.kt:87) - ⚬ readme.examples.ThirdPartySpec$1$4.invoke$toComplyValidation(ThirdPartySpec.kt:87) - ⚬ readme.examples.ThirdPartySpec$1$4.invoke(ThirdPartySpec.kt:93) - ⚬ readme.examples.ThirdPartySpec$1$4.invoke(ThirdPartySpec.kt:85) + ⚬ readme.examples.ThirdPartyExamples.validateMaxThreshold(ThirdPartyExamples.kt:86) + ⚬ readme.examples.ThirdPartyExamples$ex-third-party-10$toComplyValidation$1.invoke(ThirdPartyExamples.kt:94) + ⚬ readme.examples.ThirdPartyExamples$ex-third-party-10$toComplyValidation$1.invoke(ThirdPartyExamples.kt:92) + ⚬ readme.examples.ThirdPartyExamples.ex_third_party_10$toComplyValidation(ThirdPartyExamples.kt:92) + ⚬ readme.examples.ThirdPartyExamples.ex-third-party-10(ThirdPartyExamples.kt:98) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + ⚬ java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + ⚬ java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + ⚬ java.base/java.lang.reflect.Method.invoke(Method.java:566) ``` @@ -2394,7 +2409,7 @@ Its usage is then as follows: expect(Person("Susanne", "Whitley", 43, emptyList())) .toHaveNumberOfChildren(2) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt#L74)[Output](#ex-own-compose-3) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctions.kt#L79)[Output](#ex-own-compose-3) ```text I expected subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[]) (readme.examples.Person <1234789>) @@ -2430,7 +2445,7 @@ I.e. it fails for a `Person` with 0 children, because such a person does not hav expect(Person("Susanne", "Whitley", 43, emptyList())) .toHaveAdultChildren() ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt#L91)[Output](#ex-own-compose-4) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctions.kt#L98)[Output](#ex-own-compose-4) ```text I expected subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[]) (readme.examples.Person <1234789>) @@ -2476,7 +2491,7 @@ expect(Person("Susanne", "Whitley", 43, listOf(Person("Petra", "Whitley", 12, em feature { f(it::age) }.toBeGreaterThan(18) } ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt#L101)[Output](#ex-own-compose-5) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctions.kt#L109)[Output](#ex-own-compose-5) ```text I expected subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[Person(firstName=Petra, lastName=Whitley, age=12, children=[])]) (readme.examples.Person <1234789>) @@ -2657,7 +2672,7 @@ Following an example using the expectation verb ```kotlin expect(10).toEqual(9) ``` -↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationVerbSpec.kt#L51)[Output](#ex-own-expectation-verb) +↑ [Example](https://github.com/robstoll/atrium/tree/main/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationVerb.kt#L52)[Output](#ex-own-expectation-verb) ```text expected the subject: diff --git a/gradle/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts b/gradle/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts index d1b7f80f7f..3a9d866122 100644 --- a/gradle/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts +++ b/gradle/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts @@ -92,7 +92,7 @@ apiValidation { //misc not relevant for bc "atrium-specs", "atrium-verbs-internal", - "readme-examples" + "readme-examples", ) ) val kotlinVersion = KotlinVersion.fromVersion(buildParameters.kotlin.version) diff --git a/misc/tools/readme-examples/build.gradle.kts b/misc/tools/readme-examples/build.gradle.kts index 20aa141ee0..71b43d693e 100644 --- a/misc/tools/readme-examples/build.gradle.kts +++ b/misc/tools/readme-examples/build.gradle.kts @@ -9,9 +9,6 @@ kotlin { main { dependencies { implementation(libs.junit.platform.console) - implementation(libs.spek.jvm) - implementation(libs.spek.runner) - implementation(libs.spek.runtime) runtimeOnly(kotlin("reflect")) implementation(prefixedProject("fluent")) @@ -26,26 +23,11 @@ kotlin { } } } - tasks { - register("readme") { - group = "documentation" - description = "Runs examples, includes the code and the output in README.md" - - classpath = project.sourceSets.main.get().runtimeClasspath - val version = rootProject.version.toString() - environment("README_SOURCETREE", if (version.endsWith("-SNAPSHOT")) "tree/main" else "tree/v$version") +} - this.mainClass.set("org.junit.platform.console.ConsoleLauncher") - args = listOf( - "--scan-class-path", project.sourceSets.main.get().output.classesDirs.asPath, - "--disable-banner", - "--fail-if-no-tests", - "--include-engine", "spek2-readme", - "--details", "summary" - ) - } - check { - dependsOn("readme") - } +tasks.test.configure { + doFirst { + val version = rootProject.version.toString() + environment("README_SOURCETREE", if (version.endsWith("-SNAPSHOT")) "tree/main" else "tree/v$version") } } diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/Between1Spec.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/Between1Spec.kt deleted file mode 100644 index e97df8b97d..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/Between1Spec.kt +++ /dev/null @@ -1,32 +0,0 @@ -package readme.examples - -import ch.tutteli.atrium.api.fluent.en_GB.and -import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThanOrEqualTo -import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan -import ch.tutteli.atrium.creating.Expect -import org.spekframework.spek2.Spek -import java.util.* - -/** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers - */ -object Between1Spec : Spek({ - test("code-own-compose-1") { - fun Expect.toBeBetween(lowerBoundInclusive: T, upperBoundExclusive: T) = - and { - toBeGreaterThanOrEqualTo(lowerBoundInclusive) - toBeLessThan(upperBoundExclusive) - } - } -}) diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FaqSpec.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/FaqSpec.kt deleted file mode 100644 index 372f356aaa..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FaqSpec.kt +++ /dev/null @@ -1,30 +0,0 @@ -package readme.examples - -import ch.tutteli.atrium.api.fluent.en_GB.asIterable -import ch.tutteli.atrium.api.fluent.en_GB.feature -import ch.tutteli.atrium.api.fluent.en_GB.toContain -import org.spekframework.spek2.Spek -import readme.examples.utils.expect - -/** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers - */ -object FaqSpec : Spek({ - test("code-faq-1") { - expect(sequenceOf(1, 2, 3)).asIterable().toContain(2) - } - test("code-faq-2") { - expect(sequenceOf(1, 2, 3)).feature { f(it::asIterable) }.toContain(2) - } -}) diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt deleted file mode 100644 index 7b776e6299..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/MostExamplesSpec.kt +++ /dev/null @@ -1,278 +0,0 @@ -package readme.examples - -import readme.examples.utils.expect -import ch.tutteli.atrium.api.fluent.en_GB.* -import org.spekframework.spek2.Spek -import java.math.BigDecimal - -/** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers - */ -class MostExamplesSpec : Spek({ - - test("ex-single") { - // two single expectations, only first evaluated - expect(4 + 6).toBeLessThan(5).toBeGreaterThan(10) - } - - fun codeSingleNotExecutedHenceDoesNotFail() { - //snippet-code-single-start - expect(4 + 6).toBeLessThan(5) - expect(4 + 6).toBeGreaterThan(10) - //snippet-code-single-end - } - - test("code-single-explained") { - //snippet-code-single-insert - } - - test("ex-group") { - // expectation-group with two expectations, both evaluated - expect(4 + 6) { - toBeLessThan(5) - toBeGreaterThan(10) - } - } - - fun codeAndNotExecutedHenceDoesNotFail() { - //snippet-code-and-start - expect(5) { - // ... - } and { // if the previous block fails, then this one is not evaluated - // ... - } - //snippet-code-and-end - } - - test("code-and") { - expect(5).toBeGreaterThan(2).and.toBeLessThan(10) - - //snippet-code-and-insert - } - - test("ex-toThrow1") { - expect { - // this lambda does something but eventually... - throw IllegalArgumentException("name is empty") - }.toThrow() - } - - test("ex-toThrow2") { - expect { - throw IllegalArgumentException() - }.toThrow { - message { toStartWith("firstName") } - } - } - - test("ex-toThrow3") { - expect { - throw IllegalArgumentException() - }.toThrow().message.toStartWith("firstName") - } - - test("ex-notToThrow") { - expect { - // this block does something but eventually... - throw IllegalArgumentException("name is empty", RuntimeException("a cause")) - }.notToThrow() - } - - test("ex-type-expectations-1") { - //snippet-type-expectations-insert - expect(x).toBeAnInstanceOf { - feature { f(it::word) }.toEqual("goodbye") - feature { f(it::flag) }.toEqual(false) - } - } - test("ex-type-expectations-2") { - expect(x).toBeAnInstanceOf() - .feature { f(it::number) } - .toEqual(2) - } - - - test("ex-nullable-1") { - val slogan1: String? = "postulating expectations made easy" - expect(slogan1).toEqual(null) - } - test("ex-nullable-2") { - val slogan2: String? = null - expect(slogan2).toEqual("postulating expectations made easy") - } - val slogan2: String? = null - test("ex-nullable-3") { - expect(slogan2) // subject has type String? - .notToEqualNull() // subject is narrowed to String - .toStartWith("atrium") - } - test("ex-nullable-4") { - expect(slogan2).notToEqualNull { toStartWith("atrium") } - } - - test("ex-collection-short-1") { - expect(listOf(1, 2, 2, 4)).toContain(2, 3) - } - - test("ex-collection-short-2") { - expect(listOf(1, 2, 2, 4)).toContain( - { toBeLessThan(0) }, - { toBeGreaterThan(2).toBeLessThan(4) } - ) - } - - test("ex-collection-any") { - expect(listOf(1, 2, 3, 4)).toHaveElementsAndAny { - toBeLessThan(0) - } - } - test("ex-collection-none") { - expect(listOf(1, 2, 3, 4)).toHaveElementsAndNone { - toBeGreaterThan(2) - } - } - test("ex-collection-all") { - expect(listOf(1, 2, 3, 4)).toHaveElementsAndAll { - toBeGreaterThan(2) - } - } - - test("ex-collection-builder-1") { - expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.entries({ toBeLessThan(3) }, { toBeLessThan(2) }) - } - test("ex-collection-reportOptions-1") { - expect(listOf(1, 2, 2, 4)).toContainExactly( - { toBeLessThan(3) }, - { toBeLessThan(2) }, - { toBeGreaterThan(1) }, - report = { showOnlyFailingIfMoreExpectedElementsThan(2) } - ) - } - test("ex-collection-builder-2") { - expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.values(1, 2, 2, 3, 4) - } - test("ex-collection-builder-3") { - expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.atLeast(1).butAtMost(2).entries({ toBeLessThan(3) }) - } - test("ex-collection-builder-4") { - expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(1, 2, 3, 4) - } - test("ex-collection-builder-5") { - expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(4, 3, 2, 2, 1) - } - - test("ex-map-1") { - expect(mapOf("a" to 1, "b" to 2)).toContain("c" to 2, "a" to 1, "b" to 1) - } - test("ex-map-2") { - expect(mapOf("a" to 1, "b" to 2)).toContain( - KeyValue("c") { toEqual(2) }, - KeyValue("a") { toBeGreaterThan(2) }, - KeyValue("b") { toBeLessThan(2) } - ) - } - - test("ex-map-only-1") { - expect(mapOf("a" to 1, "b" to 2)).toContainOnly("b" to 2) - } - test("ex-map-only-2") { - expect(mapOf("a" to 1, "b" to 2)).toContainOnly( - KeyValue("c") { toEqual(2) }, - KeyValue("a") { toBeLessThan(2) }, - KeyValue("b") { toBeLessThan(2) } - ) - } - - test("ex-map-builder-1") { - expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries("b" to 2, "a" to 1) - } - test("ex-map-builder-2") { - expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries( - KeyValue("a") { toBeLessThan(2) }, - KeyValue("b") { toBeLessThan(2) }) - } - - //snippet-SimplePerson-start - data class Person(val firstName: String, val lastName: String, val age: Int) - //snippet-SimplePerson-end - - test("ex-map-3") { - //snippet-SimplePerson-insert - val bernstein = Person("Leonard", "Bernstein", 50) - expect(mapOf("bernstein" to bernstein)) - .getExisting("bernstein") { - feature { f(it::firstName) }.toEqual("Leonard") - feature { f(it::age) }.toEqual(60) - } - .getExisting("einstein") { - feature { f(it::firstName) }.toEqual("Albert") - } - } - test("ex-map-4") { - expect(mapOf("a" to 1, "b" to 2)) { - keys { toHaveElementsAndAll { toStartWith("a") } } - values { toHaveElementsAndNone { toBeGreaterThan(1) } } - } - } - test("ex-map-5") { - expect(linkedMapOf("a" to 1, "b" to 2)).asEntries().toContain.inOrder.only.entries( - { toEqualKeyValue("a", 1) }, - { - key.toStartWith("a") - value.toBeGreaterThan(2) - } - ) - } - - test("ex-because-1") { - expect("filename?") - .because("? is not allowed in file names on Windows") { - notToContain("?") - } - } - - test("exs-add-info-1") { - expect(listOf(1, 2, 3)).toContain.inOrder.only.values(1, 3) - } - test("exs-add-info-2") { - expect(9.99f).toEqualWithErrorTolerance(10.0f, 0.01f) - } - test("ex-add-info-3") { - expect { - try { - throw UnsupportedOperationException("not supported") - } catch (t: Throwable) { - throw IllegalArgumentException("no no no...", t) - } - }.toThrow { messageToContain("no no no") } - } - - test("ex-pitfall-1") { - expect(BigDecimal.TEN).toEqualIncludingScale(BigDecimal("10.0")) - } - test("ex-pitfall-2") { - expect(listOf(1)).get(0) {} - } -}) - -//@formatter:off -//snippet-type-expectations-start -interface SuperType - -data class SubType1(val number: Int) : SuperType -data class SubType2(val word: String, val flag: Boolean) : SuperType - -val x: SuperType = SubType2("hello", flag = true) -//snippet-type-expectations-end -//@formatter:on diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt deleted file mode 100644 index 8dc110ae78..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/PathSpec.kt +++ /dev/null @@ -1,54 +0,0 @@ -package readme.examples - -import ch.tutteli.atrium.api.fluent.en_GB.toBeARegularFile -import ch.tutteli.atrium.api.fluent.en_GB.toBeWritable -import ch.tutteli.atrium.api.fluent.en_GB.toExist -import ch.tutteli.niok.deleteRecursively -import org.spekframework.spek2.Spek -import readme.examples.utils.expect -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.util.* - - -/** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers - */ - -object PathSpec : Spek({ - val toDelete: MutableSet = HashSet() - - afterGroup { - toDelete.forEach { it.deleteRecursively() } - } - - test("ex-path-exists") { - expect(Paths.get("/usr/bin/noprogram")).toExist() - } - - test("ex-path-writable") { - expect(Paths.get("/root/.ssh/config")).toBeWritable() - } - - val tmpdir = Paths.get(System.getProperty("java.io.tmpdir")) - test("ex-path-symlink-and-parent-not-folder") { - val directory = Files.createDirectory(tmpdir.resolve("atrium-path")) - val file = Files.createFile(directory.resolve("file")) - val filePointer = Files.createSymbolicLink(directory.resolve("directory"), file) - - expect(filePointer.resolve("subfolder/file")).toBeARegularFile() - } - toDelete.add(tmpdir.resolve("atrium-path")) -}) diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeExecutionListener.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeExecutionListener.kt deleted file mode 100644 index 31b1a539c7..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeExecutionListener.kt +++ /dev/null @@ -1,62 +0,0 @@ -package readme.examples - -import org.spekframework.spek2.junit.JUnitEngineExecutionListenerAdapter -import org.spekframework.spek2.runtime.execution.ExecutionListener -import org.spekframework.spek2.runtime.execution.ExecutionResult -import org.spekframework.spek2.runtime.scope.TestScopeImpl - -class ReadmeExecutionListener( - private val listener: JUnitEngineExecutionListenerAdapter, - private val examples: MutableMap, - private val code: MutableSet -) : ExecutionListener by listener { - - override fun testExecutionFinish(test: TestScopeImpl, result: ExecutionResult) { - when (result) { - ExecutionResult.Success -> handleSuccess(test) - is ExecutionResult.Failure -> handleFailure(result, test) - } - } - - private fun handleSuccess(test: TestScopeImpl) { - if (!test.id.name.startsWith("code")) { - listener.testExecutionFinish( - test, - ExecutionResult.Failure(IllegalStateException("example tests are supposed to fail")) - ) - return - } - if (code.contains(test.id.name)) { - listener.testExecutionFinish( - test, - ExecutionResult.Failure(IllegalStateException("code ${test.id.name} is at least defined twice")) - ) - return - } - - code.add(test.id.name) - listener.testExecutionFinish(test, ExecutionResult.Success) - } - - private fun handleFailure(result: ExecutionResult.Failure, test: TestScopeImpl) { - if (!test.id.name.startsWith("ex")) { - listener.testExecutionFinish( - test, - ExecutionResult.Failure( - IllegalStateException( - "only example tests are supposed to fail, not ${test.id.name}", - result.cause - ) - ) - ) - return - } - when (result.cause) { - is AssertionError -> { - examples[test.id.name] = result.cause.message!! - listener.testExecutionFinish(test, ExecutionResult.Success) - } - else -> listener.testExecutionFinish(test, result) - } - } -} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeTestEngine.kt b/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeTestEngine.kt deleted file mode 100644 index 5279a9afeb..0000000000 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ReadmeTestEngine.kt +++ /dev/null @@ -1,287 +0,0 @@ -package readme.examples - -import ch.tutteli.niok.absolutePathAsString -import ch.tutteli.niok.exists -import ch.tutteli.niok.readText -import ch.tutteli.niok.writeText -import org.junit.platform.engine.* -import org.spekframework.spek2.junit.JUnitEngineExecutionListenerAdapter -import org.spekframework.spek2.junit.SpekTestDescriptor -import org.spekframework.spek2.junit.SpekTestDescriptorFactory -import org.spekframework.spek2.junit.SpekTestEngine -import org.spekframework.spek2.runtime.SpekRuntime -import org.spekframework.spek2.runtime.execution.ExecutionRequest -import java.nio.file.Paths -import java.util.* -import kotlin.collections.HashSet -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.filterIsInstance -import kotlin.collections.forEach -import kotlin.collections.isNotEmpty -import kotlin.collections.map -import kotlin.collections.mutableMapOf -import kotlin.collections.set -import org.junit.platform.engine.ExecutionRequest as JUnitExecutionRequest - -class ReadmeTestEngine : TestEngine { - private val spek = SpekTestEngine() - - private val examples = mutableMapOf() - private val code = HashSet() - private val snippets = mutableMapOf() - - override fun getId(): String = "spek2-readme" - - override fun discover(discoveryRequest: EngineDiscoveryRequest, uniqueId: UniqueId): TestDescriptor { - val descriptor = spek.discover(discoveryRequest, uniqueId) - require(descriptor.children.isNotEmpty()) { - "Could not find any specification, check your runtime classpath" - } - return descriptor - } - - override fun execute(request: JUnitExecutionRequest) { - val default = Locale.getDefault() - try { - Locale.setDefault(Locale.UK) - - val classes = runSpekWithCustomListener(request) - - processExamples(classes, request) - } catch (t: Throwable) { - t.printStackTrace() - Locale.setDefault(default) - - request.fail(t) - } - } - - private fun processExamples(classes: List, request: JUnitExecutionRequest) { - - val specContents = classes.map { qualifiedClass -> - qualifiedClass to fileContent("src/main/kotlin/$qualifiedClass.kt", request) - } - specContents.forEach { (_, specContent) -> - extractSnippets(specContent, request) - } - - var readmeContent = fileContent(readmeStringPath, request) - - if (examples.isEmpty()) { - request.fail("no examples found") - return - } - if (code.isEmpty()) { - request.fail("no code found") - return - } - if (snippets.isEmpty()) { - request.fail("no snippets found") - return - } - - examples.forEach { (exampleId, output) -> - readmeContent = updateExampleLikeInReadme(readmeContent, specContents, exampleId, output, request) - } - code.forEach { codeId -> - readmeContent = updateCodeInReadme(readmeContent, specContents, codeId, request) - } - snippets.forEach { (snippetId, snippetContent) -> - readmeContent = updateSnippetInReadme(readmeContent, snippetId, snippetContent, request) - } - Paths.get(readmeStringPath).writeText(readmeContent) - } - - private fun extractSnippets(specContent: String, request: JUnitExecutionRequest) { - Regex("//(snippet-.+)-start([\\S\\s]*?)//(snippet-.+)-end").findAll(specContent).forEach { - val (tag, content, endTag) = it.destructured - request.failIf(tag != endTag) { "tag $tag-start did not end with $tag-end but with $endTag" } - snippets[tag] = content.trimIndent() - } - } - - private fun runSpekWithCustomListener(request: JUnitExecutionRequest) : List { - val roots = request.rootTestDescriptor.children - .filterIsInstance() - .map(SpekTestDescriptor::scope) - - val executionListener = ReadmeExecutionListener( - JUnitEngineExecutionListenerAdapter(request.engineExecutionListener, SpekTestDescriptorFactory()), - examples, - code - ) - val executionRequest = ExecutionRequest(roots, executionListener) - SpekRuntime().execute(executionRequest) - return roots.map { it.path.toString() } - } - - private fun fileContent(path: String, request: JUnitExecutionRequest): String { - val file = Paths.get(path) - request.failIf(!file.exists) { "could not find ${file.absolutePathAsString}" } - return file.readText() - } - - private inline fun JUnitExecutionRequest.failIf(predicate: Boolean, errorMessage: () -> String) { - if (predicate) fail(errorMessage()) - } - - private fun JUnitExecutionRequest.fail(errorMessage: String) = fail(IllegalStateException(errorMessage)) - private fun JUnitExecutionRequest.fail(throwable: Throwable) { - engineExecutionListener.executionFinished( - rootTestDescriptor, - TestExecutionResult.failed(throwable) - ) - } - - - private fun updateExampleLikeInReadme( - readmeContent: String, - specContents: List>, - exampleId: String, - output: String, - request: JUnitExecutionRequest - ): String = when { - exampleId.startsWith("ex-") -> - updateExampleInReadme(readmeContent, specContents, exampleId, output, request) - exampleId.startsWith("exs-") -> - updateSplitExampleInReadme(readmeContent, specContents, exampleId, output, request) - else -> { - request.fail("unknown example kind $exampleId") - readmeContent - } - } - - private fun updateExampleInReadme( - readmeContent: String, - specContents: List>, - exampleId: String, - output: String, - request: JUnitExecutionRequest - ): String = updateTagInReadme(readmeContent, specContents, exampleId, request, "example") { qualifiedClass, lineNumber, sourceCode -> - """```kotlin - |$sourceCode - |``` - |↑ [Example](https://github.com/robstoll/atrium/${System.getenv("README_SOURCETREE")}/misc/tools/readme-examples/src/main/kotlin/$qualifiedClass.kt#L$lineNumber)[Output](#$exampleId) - | - |```text - |$output - |``` - """.trimMargin() - } - - - private fun updateSplitExampleInReadme( - readmeContent: String, - specContents: List>, - exampleId: String, - output: String, - request: JUnitExecutionRequest - ): String { - val updatedReadme = updateTagInReadme( - readmeContent, specContents, exampleId, request, "ex-code" - ) { _, _, sourceCode -> - """```kotlin - |$sourceCode - |```""".trimMargin() - } - - return updateTagInReadme(updatedReadme, "$exampleId-output", request, "exs-output") { - """```text - |$output - |```""".trimMargin() - } - } - - private fun updateTagInReadme( - readmeContent: String, - specContents: List>, - tag: String, - request: org.junit.platform.engine.ExecutionRequest, - kind: String, - content: (String, Int, String) -> String - ): String = updateTagInReadme(readmeContent, tag, request, kind) { - val (qualifiedClass, lineNumber, sourceCode) = extractSourceCode(specContents, tag, request) - content(qualifiedClass, lineNumber, sourceCode) - } - - private fun updateTagInReadme( - readmeContent: String, - tag: String, - request: org.junit.platform.engine.ExecutionRequest, - kind: String, - contentProvider: () -> String - ): String { - val tagRegex = Regex("( *)<$tag>[\\S\\s]*") - return if (!tagRegex.containsMatchIn(readmeContent)) { - request.fail("$kind tag <$tag> not found in $readmeStringPath") - readmeContent - } else { - tagRegex.replace(readmeContent) { - """<$tag> - | - |${contentProvider()} - | - """.trimMargin().prependIndent(it.destructured.component1()) - } - } - } - - private fun extractSourceCode( - specContents: List>, - testId: String, - request: JUnitExecutionRequest - ): Triple { - var lineNumber: Int? = null - val sb = StringBuilder() - - specContents.forEach { (qualifiedClass, specContent) -> - specContent.lineSequence().forEachIndexed { index, line -> - if (lineNumber != null) { - if (line.startsWith(" }")) return Triple(qualifiedClass, lineNumber!!, sb.toString().trimIndent()) - sb.append(line).append("\n") - - } else if (line.trim().startsWith("test(\"$testId\")")) { - lineNumber = index + 1 - } - } - } - request.fail("cannot find source code for $testId") - return Triple("", -1, "") - } - - private fun updateCodeInReadme( - readmeContent: String, - specContents: List>, - snippetId: String, - request: org.junit.platform.engine.ExecutionRequest - ): String = updateTagInReadme(readmeContent, specContents, snippetId, request, "code") { _, _, sourceCode -> - - """```kotlin - |$sourceCode - |``` - """.trimMargin() - } - - - private fun updateSnippetInReadme( - readmeContent: String, - snippetId: String, - snippetContent: String, - request: JUnitExecutionRequest - ): String { - - val snippet = Regex("//$snippetId-insert") - return if (!snippet.containsMatchIn(readmeContent)) { - request.fail("$snippetId is never inserted in $readmeStringPath") - readmeContent - } else { - snippet.replace(readmeContent) { snippetContent } - } - } - - companion object { - private const val readmeStringPath = "../../../README.md" - } -} - diff --git a/misc/tools/readme-examples/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/misc/tools/readme-examples/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 59e1e8d713..0000000000 --- a/misc/tools/readme-examples/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -readme.examples.ReadmeTestEngine diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/BetweenExample.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/BetweenExample.kt new file mode 100644 index 0000000000..21c7f36de1 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/BetweenExample.kt @@ -0,0 +1,24 @@ +package readme.examples + +import ch.tutteli.atrium.api.fluent.en_GB.and +import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThanOrEqualTo +import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan +import ch.tutteli.atrium.creating.Expect +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import java.util.* + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class BetweenExample : ReadmeTest { + @Test + fun `code-own-compose-1`() { + fun Expect.toBeBetween(lowerBoundInclusive: T, upperBoundExclusive: T) = + and { + toBeGreaterThanOrEqualTo(lowerBoundInclusive) + toBeLessThan(upperBoundExclusive) + } + } +} + diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/CollectionExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/CollectionExamples.kt new file mode 100644 index 0000000000..3f8743c46d --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/CollectionExamples.kt @@ -0,0 +1,80 @@ +package readme.examples + +import readme.examples.utils.expect +import ch.tutteli.atrium.api.fluent.en_GB.* +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class CollectionExamples : ReadmeTest { + @Test + fun `ex-collection-short-1`() { + expect(listOf(1, 2, 2, 4)).toContain(2, 3) + } + + @Test + fun `ex-collection-short-2`() { + expect(listOf(1, 2, 2, 4)).toContain( + { toBeLessThan(0) }, + { toBeGreaterThan(2).toBeLessThan(4) } + ) + } + + @Test + fun `ex-collection-any`() { + expect(listOf(1, 2, 3, 4)).toHaveElementsAndAny { + toBeLessThan(0) + } + } + + @Test + fun `ex-collection-none`() { + expect(listOf(1, 2, 3, 4)).toHaveElementsAndNone { + toBeGreaterThan(2) + } + } + + @Test + fun `ex-collection-all`() { + expect(listOf(1, 2, 3, 4)).toHaveElementsAndAll { + toBeGreaterThan(2) + } + } + + @Test + fun `ex-collection-builder-1`() { + expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.entries({ toBeLessThan(3) }, { toBeLessThan(2) }) + } + + @Test + fun `ex-collection-reportOptions-1`() { + expect(listOf(1, 2, 2, 4)).toContainExactly( + { toBeLessThan(3) }, + { toBeLessThan(2) }, + { toBeGreaterThan(1) }, + report = { showOnlyFailingIfMoreExpectedElementsThan(2) } + ) + } + + @Test + fun `ex-collection-builder-2`() { + expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.values(1, 2, 2, 3, 4) + } + + @Test + fun `ex-collection-builder-3`() { + expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.atLeast(1).butAtMost(2).entries({ toBeLessThan(3) }) + } + + @Test + fun `ex-collection-builder-4`() { + expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(1, 2, 3, 4) + } + + @Test + fun `ex-collection-builder-5`() { + expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(4, 3, 2, 2, 1) + } +} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/DataDrivenExamples.kt similarity index 75% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/DataDrivenExamples.kt index 64ab1d91ac..57363ed715 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/DataDrivenSpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/DataDrivenExamples.kt @@ -4,29 +4,20 @@ import readme.examples.utils.expect import readme.examples.utils.expectGrouped import ch.tutteli.atrium.api.fluent.en_GB.* import ch.tutteli.atrium.creating.ExpectationCreator -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest /** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * See [ReadmeTest] on how these tests are written into README.md */ -class DataDrivenSpec : Spek({ +class DataDrivenExamples : ReadmeTest { //snippet-data-driven-1-start fun myFun(i: Int) = (i + 97).toChar() //snippet-data-driven-1-end - test("ex-data-driven-1") { + @Test + fun `ex-data-driven-1`() { //snippet-data-driven-1-insert expectGrouped { @@ -42,7 +33,8 @@ class DataDrivenSpec : Spek({ } } - test("ex-data-driven-2") { + @Test + fun `ex-data-driven-2`() { expectGrouped { mapOf>( 1 to { toBeLessThan('f') }, @@ -60,7 +52,8 @@ class DataDrivenSpec : Spek({ fun myNullableFun(i: Int) = if (i > 0) i.toString() else null //snippet-data-driven-3-end - test("ex-data-driven-3") { + @Test + fun `ex-data-driven-3`() { //snippet-data-driven-3-insert expectGrouped { @@ -79,7 +72,8 @@ class DataDrivenSpec : Spek({ } } - test("ex-data-driven-nesting") { + @Test + fun `ex-data-driven-nesting`() { val x1 = 1 val x2 = 3 val y = 6 @@ -104,6 +98,6 @@ class DataDrivenSpec : Spek({ } } } -}) +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/FaqExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FaqExamples.kt new file mode 100644 index 0000000000..9e21996e12 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FaqExamples.kt @@ -0,0 +1,22 @@ +package readme.examples + +import ch.tutteli.atrium.api.fluent.en_GB.asIterable +import ch.tutteli.atrium.api.fluent.en_GB.feature +import ch.tutteli.atrium.api.fluent.en_GB.toContain +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import readme.examples.utils.expect + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class FaqExamples : ReadmeTest { + @Test + fun `code-faq-1`() { + expect(sequenceOf(1, 2, 3)).asIterable().toContain(2) + } + @Test + fun `code-faq-2`() { + expect(sequenceOf(1, 2, 3)).feature { f(it::asIterable) }.toContain(2) + } +} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FeatureExtractorExamples.kt similarity index 82% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/FeatureExtractorExamples.kt index fe47feb73f..7f246c6e9e 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FeatureExtractorSpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FeatureExtractorExamples.kt @@ -2,25 +2,15 @@ package readme.examples import ch.tutteli.atrium.api.fluent.en_GB.* import ch.tutteli.atrium.creating.Expect -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest import readme.examples.utils.expect import java.util.* /** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * See [ReadmeTest] on how these tests are written into README.md */ -class FeatureExtractorSpec : Spek({ +class FeatureExtractorExamples : ReadmeTest { //snippet-Person-start data class Person(val firstName: String, val lastName: String, val isStudent: Boolean) { @@ -34,11 +24,13 @@ class FeatureExtractorSpec : Spek({ val myPerson = Person("Robert", "Stoll", false) //snippet-Person-end - test("code-Person") { + @Test + fun `code-Person`() { //snippet-Person-insert } - test("ex-its-single") { + @Test + fun `ex-its-single`() { expect(myPerson) .its({ isStudent }) { toEqual(true) } // fails, subject still Person afterwards .its { fullName() } // not evaluated anymore, subject String afterwards @@ -46,7 +38,8 @@ class FeatureExtractorSpec : Spek({ } //@formatter:off - test("ex-its-group") { + @Test + fun `ex-its-group`() { expect(myPerson) { // forms an expectation-group its({ firstName }) { // forms an expectation-group @@ -60,7 +53,8 @@ class FeatureExtractorSpec : Spek({ } //@formatter:on - test("ex-property-methods-single") { + @Test + fun `ex-property-methods-single`() { expect(myPerson) .feature({ f(it::isStudent) }) { toEqual(true) } // fails, subject still Person afterwards .feature { f(it::fullName) } // not evaluated anymore, subject String afterwards @@ -68,7 +62,8 @@ class FeatureExtractorSpec : Spek({ } //@formatter:off - test("ex-property-methods-group") { + @Test + fun `ex-property-methods-group`() { expect(myPerson) { // forms an expectation-group feature({ f(it::firstName) }) { // forms an expectation-group @@ -82,7 +77,8 @@ class FeatureExtractorSpec : Spek({ } //@formatter:on - test("ex-methods-args") { + @Test + fun `ex-methods-args`() { expect(myPerson) .feature { f(it::nickname, false) } // subject narrowed to String .toEqual("Robert aka. Stoll") // fails @@ -99,7 +95,8 @@ class FeatureExtractorSpec : Spek({ //snippet-Family-end //@formatter:on - test("ex-arbitrary-features") { + @Test + fun `ex-arbitrary-features`() { //snippet-Family-insert expect(myFamily) .feature("the number of members", { members.size }) { toEqual(1) } // subject still Family afterwards @@ -115,7 +112,8 @@ class FeatureExtractorSpec : Spek({ feature(Pair::first) { toEqual(expected) } //snippet-within-funs-end - test("ex-within-expectation-functions") { + @Test + fun `ex-within-expectation-functions`() { //snippet-within-funs-insert expect(listOf(1 to "a", 2 to "b")).get(10) { @@ -136,7 +134,8 @@ class FeatureExtractorSpec : Spek({ } //snippet-worst-case-end - test("code-ambiguity-problems") { + @Test + fun `code-ambiguity-problems`() { //snippet-worst-case-insert expect(WorstCase()) { @@ -161,7 +160,8 @@ class FeatureExtractorSpec : Spek({ val dataGenerator = DataGenerator() - test("code-extractSubject"){ + @Test + fun `code-extractSubject`() { val persons = dataGenerator.getRandomPersonsWithChildren() expect(persons).toHaveElementsAndAll { extractSubject { person -> @@ -173,4 +173,4 @@ class FeatureExtractorSpec : Spek({ } } } -}) +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/FirstExample.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FirstExample.kt new file mode 100644 index 0000000000..4ac37708e8 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/FirstExample.kt @@ -0,0 +1,26 @@ +package readme.examples//@formatter:off +//snippet-import-start +import ch.tutteli.atrium.api.fluent.en_GB.* +import ch.tutteli.atrium.api.verbs.expect +//snippet-import-end +//@formatter:on + +import ch.tutteli.atrium.creating.Expect +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class FirstExample : ReadmeTest { + + fun expect(t: T): Expect = readme.examples.utils.expect(t) + + @Test + fun `ex-first`() { + //snippet-import-insert + + val x = 10 + expect(x).toEqual(9) + } +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/MapExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/MapExamples.kt new file mode 100644 index 0000000000..db670e73cb --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/MapExamples.kt @@ -0,0 +1,90 @@ +package readme.examples + +import readme.examples.utils.expect +import ch.tutteli.atrium.api.fluent.en_GB.* +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import java.math.BigDecimal + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class MapExamples : ReadmeTest { + + @Test + fun `ex-map-1`() { + expect(mapOf("a" to 1, "b" to 2)).toContain("c" to 2, "a" to 1, "b" to 1) + } + + @Test + fun `ex-map-2`() { + expect(mapOf("a" to 1, "b" to 2)).toContain( + KeyValue("c") { toEqual(2) }, + KeyValue("a") { toBeGreaterThan(2) }, + KeyValue("b") { toBeLessThan(2) } + ) + } + + @Test + fun `ex-map-only-1`() { + expect(mapOf("a" to 1, "b" to 2)).toContainOnly("b" to 2) + } + + @Test + fun `ex-map-only-2`() { + expect(mapOf("a" to 1, "b" to 2)).toContainOnly( + KeyValue("c") { toEqual(2) }, + KeyValue("a") { toBeLessThan(2) }, + KeyValue("b") { toBeLessThan(2) } + ) + } + + @Test + fun `ex-map-builder-1`() { + expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries("b" to 2, "a" to 1) + } + + @Test + fun `ex-map-builder-2`() { + expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries( + KeyValue("a") { toBeLessThan(2) }, + KeyValue("b") { toBeLessThan(2) }) + } + + //snippet-SimplePerson-start + data class Person(val firstName: String, val lastName: String, val age: Int) + //snippet-SimplePerson-end + + @Test + fun `ex-map-3`() { + //snippet-SimplePerson-insert + val bernstein = Person("Leonard", "Bernstein", 50) + expect(mapOf("bernstein" to bernstein)) + .getExisting("bernstein") { + feature { f(it::firstName) }.toEqual("Leonard") + feature { f(it::age) }.toEqual(60) + } + .getExisting("einstein") { + feature { f(it::firstName) }.toEqual("Albert") + } + } + + @Test + fun `ex-map-4`() { + expect(mapOf("a" to 1, "b" to 2)) { + keys { toHaveElementsAndAll { toStartWith("a") } } + values { toHaveElementsAndNone { toBeGreaterThan(1) } } + } + } + + @Test + fun `ex-map-5`() { + expect(linkedMapOf("a" to 1, "b" to 2)).asEntries().toContain.inOrder.only.entries( + { toEqualKeyValue("a", 1) }, + { + key.toStartWith("a") + value.toBeGreaterThan(2) + } + ) + } +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/MostExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/MostExamples.kt new file mode 100644 index 0000000000..655d386885 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/MostExamples.kt @@ -0,0 +1,141 @@ +package readme.examples + +import readme.examples.utils.expect +import ch.tutteli.atrium.api.fluent.en_GB.* +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import java.math.BigDecimal + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class MostExamples : ReadmeTest { + + @Test + fun `ex-single`() { + // two single expectations, only first evaluated + expect(4 + 6).toBeLessThan(5).toBeGreaterThan(10) + } + + fun codeSingleNotExecutedHenceDoesNotFail() { + //snippet-code-single-start + expect(4 + 6).toBeLessThan(5) + expect(4 + 6).toBeGreaterThan(10) + //snippet-code-single-end + } + + @Test + fun `code-single-explained`() { + //snippet-code-single-insert + } + + @Test + fun `ex-group`() { + // expectation-group with two expectations, both evaluated + expect(4 + 6) { + toBeLessThan(5) + toBeGreaterThan(10) + } + } + + fun codeAndNotExecutedHenceDoesNotFail() { + //snippet-code-and-start + expect(5) { + // ... + } and { // if the previous block fails, then this one is not evaluated + // ... + } + //snippet-code-and-end + } + + @Test + fun `code-and`() { + expect(5).toBeGreaterThan(2).and.toBeLessThan(10) + + //snippet-code-and-insert + } + + @Test + fun `ex-type-expectations-1`() { + //snippet-type-expectations-insert + expect(x).toBeAnInstanceOf { + feature { f(it::word) }.toEqual("goodbye") + feature { f(it::flag) }.toEqual(false) + } + } + @Test + fun `ex-type-expectations-2`() { + expect(x).toBeAnInstanceOf() + .feature { f(it::number) } + .toEqual(2) + } + + @Test + fun `ex-nullable-1`() { + val slogan1: String? = "postulating expectations made easy" + expect(slogan1).toEqual(null) + } + @Test + fun `ex-nullable-2`() { + val slogan2: String? = null + expect(slogan2).toEqual("postulating expectations made easy") + } + val slogan2: String? = null + @Test + fun `ex-nullable-3`() { + expect(slogan2) // subject has type String? + .notToEqualNull() // subject is narrowed to String + .toStartWith("atrium") + } + @Test + fun `ex-nullable-4`() { + expect(slogan2).notToEqualNull { toStartWith("atrium") } + } + + @Test + fun `ex-because-1`() { + expect("filename?") + .because("? is not allowed in file names on Windows") { + notToContain("?") + } + } + + @Test + fun `exs-add-info-1`() { + expect(listOf(1, 2, 3)).toContain.inOrder.only.values(1, 3) + } + @Test + fun `exs-add-info-2`() { + expect(9.99f).toEqualWithErrorTolerance(10.0f, 0.01f) + } + @Test + fun `ex-add-info-3`() { + expect { + try { + throw UnsupportedOperationException("not supported") + } catch (t: Throwable) { + throw IllegalArgumentException("no no no...", t) + } + }.toThrow { messageToContain("no no no") } + } + + @Test + fun `ex-pitfall-1`() { + expect(BigDecimal.TEN).toEqualIncludingScale(BigDecimal("10.0")) + } + @Test + fun `ex-pitfall-2`() { + expect(listOf(1)).get(0) {} + } +} + +//@formatter:off +//snippet-type-expectations-start +interface SuperType + +data class SubType1(val number: Int) : SuperType +data class SubType2(val word: String, val flag: Boolean) : SuperType + +val x: SuperType = SubType2("hello", flag = true) +//snippet-type-expectations-end +//@formatter:on diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationFunctions.kt similarity index 77% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationFunctions.kt index 9341edb65f..ba60dd8af0 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationFunctionsSpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationFunctions.kt @@ -1,6 +1,4 @@ -package readme.examples - -//@formatter:off +package readme.examples//@formatter:off //snippet-mapArguments-start import ch.tutteli.atrium.logic.utils.mapArguments //snippet-mapArguments-end @@ -12,36 +10,27 @@ import ch.tutteli.atrium.logic._logic import ch.tutteli.atrium.api.fluent.en_GB.* import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.reporting.Text -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest import readme.examples.utils.expect /** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * See [ReadmeTest] on how these tests are written into README.md */ - -object OwnExpectationFunctionsSpec : Spek({ +class OwnExpectationFunctions : ReadmeTest { //snippet-own-boolean-1-start fun Expect.toBeAMultipleOf(base: Int) = _logic.createAndAppend("is multiple of", base) { it % base == 0 } //snippet-own-boolean-1-end - test("code-own-boolean-1") { + @Test + fun `code-own-boolean-1`() { //snippet-own-boolean-import-insert //snippet-own-boolean-1-insert } - test("ex-own-boolean-1") { + @Test + fun `ex-own-boolean-1`() { expect(12).toBeAMultipleOf(5) } @@ -49,16 +38,18 @@ object OwnExpectationFunctionsSpec : Spek({ fun Expect.toBeEven() = _logic.createAndAppend("is", Text("an even number")) { it % 2 == 0 } //snippet-own-boolean-2-end - test("code-own-boolean-2") { + @Test + fun `code-own-boolean-2`() { //snippet-own-boolean-import-insert //snippet-own-boolean-2-insert } - test("ex-own-boolean-2") { + @Test + fun `ex-own-boolean-2`() { expect(13).toBeEven() } - - test("code-own-compose-3a") { + @Test + fun `code-own-compose-3a`() { //snippet-own-Person-insert } @@ -67,11 +58,13 @@ object OwnExpectationFunctionsSpec : Spek({ feature(Person::children) { toHaveSize(number) } //snippet-own-compose-3b-end - test("code-own-compose-3b") { + @Test + fun `code-own-compose-3b`() { //snippet-own-compose-3b-insert } - test("ex-own-compose-3") { + @Test + fun `ex-own-compose-3`() { expect(Person("Susanne", "Whitley", 43, emptyList())) .toHaveNumberOfChildren(2) } @@ -85,20 +78,23 @@ object OwnExpectationFunctionsSpec : Spek({ } //snippet-own-compose-4-end - test("code-own-compose-4") { + @Test + fun `code-own-compose-4`() { //snippet-own-compose-4-insert } - test("ex-own-compose-4") { + @Test + fun `ex-own-compose-4`() { expect(Person("Susanne", "Whitley", 43, emptyList())) .toHaveAdultChildren() } - - test("code-own-compose-5") { + @Test + fun `code-own-compose-5`() { //snippet-children-insert } //@formatter:off - test("ex-own-compose-5"){ + @Test + fun `ex-own-compose-5`() { expect(Person("Susanne", "Whitley", 43, listOf(Person("Petra", "Whitley", 12, emptyList())))) .children { // using the fun -> expectation-group, ergo sub expectations don't fail fast toHaveElementsAndNone { @@ -126,12 +122,14 @@ object OwnExpectationFunctionsSpec : Spek({ } //snippet-own-compose-6-end - test("code-own-compose-6") { + @Test + fun `code-own-compose-6`() { //snippet-mapArguments-insert //snippet-own-compose-6-insert } - test("code-own-compose-7") { + @Test + fun `code-own-compose-7`() { fun >> Expect.sameInitialsAs( person: Person, vararg otherPersons: Person ): Expect { @@ -142,7 +140,7 @@ object OwnExpectationFunctionsSpec : Spek({ return toContain.inOrder.only.entries(first, *others) } } -}) +} //snippet-own-Person-start data class Person( diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationVerbSpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationVerb.kt similarity index 67% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationVerbSpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationVerb.kt index 83a5c81c8d..bcf8c177dc 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/OwnExpectationVerbSpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/OwnExpectationVerb.kt @@ -1,6 +1,4 @@ -package readme.examples - -//@formatter:off +package readme.examples//@formatter:off //snippet-own-expectation-verb-import-start import ch.tutteli.atrium.core.ExperimentalNewExpectTypes import ch.tutteli.atrium.creating.ExperimentalComponentFactoryContainer @@ -16,27 +14,17 @@ import ch.tutteli.atrium.creating.RootExpect import ch.tutteli.atrium.logic.creating.RootExpectBuilder import ch.tutteli.atrium.reporting.ObjectFormatter import ch.tutteli.atrium.reporting.text.TextAssertionPairFormatter -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest import readme.examples.utils.ReadmeObjectFormatter import readme.examples.expect as expectWithNewLine /** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * See [ReadmeTest] on how these tests are written into README.md */ - -object OwnExpectationVerbSpec : Spek({ - test("code-own-expectation-verb") { +class OwnExpectationVerb: ReadmeTest { + @Test + fun `code-own-expectation-verb`() { //snippet-own-expectation-verb-import-insert //snippet-own-expectation-verb-insert @@ -48,10 +36,11 @@ object OwnExpectationVerbSpec : Spek({ withComponent(ObjectFormatter::class) { c -> ReadmeObjectFormatter(c.build()) } } - test("ex-own-expectation-verb") { + @Test + fun `ex-own-expectation-verb`() { expect(10).toEqual(9) } -}) +} //snippet-own-expectation-verb-start @OptIn(ExperimentalNewExpectTypes::class, ExperimentalComponentFactoryContainer::class) diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/PathExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/PathExamples.kt new file mode 100644 index 0000000000..47eb6aaa7e --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/PathExamples.kt @@ -0,0 +1,48 @@ +package readme.examples + +import ch.tutteli.atrium.api.fluent.en_GB.toBeARegularFile +import ch.tutteli.atrium.api.fluent.en_GB.toBeWritable +import ch.tutteli.atrium.api.fluent.en_GB.toExist +import ch.tutteli.niok.deleteRecursively +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import readme.examples.utils.expect +import java.nio.file.Files +import java.nio.file.Paths + + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class PathExamples : ReadmeTest { + + companion object { + val tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("atrium-path") + + @AfterAll + @JvmStatic + fun deletePaths() { + tmpDir.deleteRecursively() + } + } + + @Test + fun `ex-path-exists`() { + expect(Paths.get("/usr/bin/noprogram")).toExist() + } + + @Test + fun `ex-path-writable`() { + expect(Paths.get("/root/.ssh/config")).toBeWritable() + } + + @Test + fun `ex-path-symlink-and-parent-not-folder`() { + val directory = Files.createDirectory(tmpDir) + val file = Files.createFile(directory.resolve("file")) + val filePointer = Files.createSymbolicLink(directory.resolve("directory"), file) + + expect(filePointer.resolve("subfolder/file")).toBeARegularFile() + } +} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/ThirdPartyExamples.kt similarity index 74% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/ThirdPartyExamples.kt index 0f362dbd6b..a42be431a5 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/ThirdPartySpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/ThirdPartyExamples.kt @@ -3,34 +3,25 @@ package readme.examples import ch.tutteli.atrium.api.fluent.en_GB.* import ch.tutteli.atrium.creating.Expect //@formatter:off -//snippet-import-start +//snippet-logic-import-start import ch.tutteli.atrium.logic._logic -//snippet-import-end +//snippet-logic-import-end //@formatter:on import ch.tutteli.atrium.reporting.Text import org.assertj.core.api.Assertions.assertThat -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest import readme.examples.utils.expect import java.math.BigDecimal import kotlin.math.sign /** - * The tests and error message are written here and automatically placed into the README via generation. - * The generation is done during the project built. To trigger it manually, you have to run: - * ``` - * ./gradlew :readme-examples:build - * ``` - * - * There are currently three kind of tags supported: - * - => places code and output into the tag - * - => places code into the tag, output will be put into a tag named - * - => is not supposed to fail and only the code is put into the code - * - * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * See [ReadmeTest] on how these tests are written into README.md */ -object ThirdPartySpec : Spek({ +class ThirdPartyExamples : ReadmeTest { - test("ex-third-party-1") { + @Test + fun `ex-third-party-1`() { expect(listOf(1, 2, 3, -1)).toHaveElementsAndAll { toHoldThirdPartyExpectation("not to be", Text("negative")) { subject -> // in the following we use assertJ @@ -39,7 +30,8 @@ object ThirdPartySpec : Spek({ } } - test("ex-third-party-2") { + @Test + fun `ex-third-party-2`() { fun Expect.notToBeNegative() = toHoldThirdPartyExpectation("not to be", Text("negative")) { subject -> when (subject) { @@ -56,8 +48,9 @@ object ThirdPartySpec : Spek({ expect(-10).notToBeNegative() } - test("ex-third-party-3") { - //snippet-import-insert + @Test + fun `ex-third-party-3`() { + //snippet-logic-import-insert fun Expect.notToBeNegative() = _logic.createAndAppend("not to be", Text("negative")) { subject -> @@ -82,7 +75,8 @@ object ThirdPartySpec : Spek({ throw IllegalStateException("threshold value for alpha1 exceeded, expected <= 1000, was $alpha1") } - test("ex-third-party-10") { + @Test + fun `ex-third-party-10`() { fun Expect.toComplyValidation() = toHoldThirdPartyExpectation("to comply", Text("validation")) { subject -> subject.validateMinThreshold() @@ -92,6 +86,6 @@ object ThirdPartySpec : Spek({ expect(MyDomainModel(alpha1 = 1204)).toComplyValidation() } -}) +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/ToThrowExamples.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/ToThrowExamples.kt new file mode 100644 index 0000000000..f15d454e4f --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/ToThrowExamples.kt @@ -0,0 +1,45 @@ +package readme.examples + +import readme.examples.utils.expect +import ch.tutteli.atrium.api.fluent.en_GB.* +import org.junit.jupiter.api.Test +import readme.examples.jupiter.ReadmeTest +import java.math.BigDecimal + +/** + * See [ReadmeTest] on how these tests are written into README.md + */ +class ToThrowExamples : ReadmeTest { + + @Test + fun `ex-toThrow1`() { + expect { + // this lambda does something but eventually... + throw IllegalArgumentException("name is empty") + }.toThrow() + } + + @Test + fun `ex-toThrow2`() { + expect { + throw IllegalArgumentException() + }.toThrow { + message { toStartWith("firstName") } + } + } + + @Test + fun `ex-toThrow3`() { + expect { + throw IllegalArgumentException() + }.toThrow().message.toStartWith("firstName") + } + + @Test + fun `ex-notToThrow`() { + expect { + // this block does something but eventually... + throw IllegalArgumentException("name is empty", RuntimeException("a cause")) + }.notToThrow() + } +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeInvocationInterceptor.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeInvocationInterceptor.kt new file mode 100644 index 0000000000..6c435c4c9f --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeInvocationInterceptor.kt @@ -0,0 +1,79 @@ +package readme.examples.jupiter + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.InvocationInterceptor +import org.junit.jupiter.api.extension.ReflectiveInvocationContext +import java.lang.reflect.Method +import java.util.* + +class ReadmeInvocationInterceptor : InvocationInterceptor { + override fun interceptTestMethod( + invocation: InvocationInterceptor.Invocation, + invocationContext: ReflectiveInvocationContext, + extensionContext: ExtensionContext? + ) { + // we only intercept ReadmeTests + if (ReadmeTest::class.java.isAssignableFrom(invocationContext.executable.declaringClass)) { + val failure = executeAndCatch(invocation, invocationContext, extensionContext) + ReadmeState.testClasses.add(invocationContext.executable.declaringClass) + + val testName = invocationContext.executable.name + if (testName.startsWith("code-")) { + handleCode(failure, testName) + } else if (testName.startsWith("ex-") || testName.startsWith("exs-")) { + handleExample(failure, testName) + } else { + failure.ifPresent { + throw AssertionError( + "only example tests are supposed to fail, test $testName does neither start with ex- nor with exs-", + it + ) + } + } + } else { + super.interceptTestMethod(invocation, invocationContext, extensionContext) + } + } + + private fun executeAndCatch( + invocation: InvocationInterceptor.Invocation, + invocationContext: ReflectiveInvocationContext, + extensionContext: ExtensionContext? + ): Optional { + val failure = try { + super.interceptTestMethod(invocation, invocationContext, extensionContext) + Optional.empty() + } catch (e: Throwable) { + Optional.of(e) + } + return failure + } + + private fun handleCode(failure: Optional, testName: String) { + // code should not fail + failure.ifPresent { throw it } + if (ReadmeState.code.contains(testName)) { + throw IllegalStateException("code $testName is at least defined twice") + } else { + ReadmeState.code.add(testName) + } + } + + private fun handleExample(failure: Optional, testName: String) { + failure.ifPresentOrElse( + { + when (it) { + is AssertionError -> { + ReadmeState.examples[testName] = it.message!! + // swallow exception, i.e. turn failing into successful + } + + else -> throw it + } + }, + { throw IllegalStateException("example tests are supposed to fail") } + ) + } + + +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeState.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeState.kt new file mode 100644 index 0000000000..fcfa2bccd1 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeState.kt @@ -0,0 +1,9 @@ +package readme.examples.jupiter + +object ReadmeState { + // not thread safe, if we should execute them in parallel then we would need to adjust this + val examples: MutableMap = mutableMapOf() + val code: MutableSet = HashSet() + val testClasses: MutableSet> = HashSet() + val snippets: MutableMap = mutableMapOf() +} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FirstExampleSpec.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTest.kt similarity index 56% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/FirstExampleSpec.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTest.kt index 95b88949b5..73d77e4aec 100644 --- a/misc/tools/readme-examples/src/main/kotlin/readme/examples/FirstExampleSpec.kt +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTest.kt @@ -1,14 +1,7 @@ -package readme.examples +package readme.examples.jupiter -//@formatter:off -//snippet-import-start -import ch.tutteli.atrium.api.fluent.en_GB.* -import ch.tutteli.atrium.api.verbs.expect -//snippet-import-end -//@formatter:on - -import ch.tutteli.atrium.creating.Expect -import org.spekframework.spek2.Spek +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.extension.ExtendWith /** * The tests and error message are written here and automatically placed into the README via generation. @@ -23,15 +16,16 @@ import org.spekframework.spek2.Spek * - => is not supposed to fail and only the code is put into the code * * Moreover, all tags can reuse snippets defined in this file with corresponding markers + * //snippet-xy-start + * ... + * //snippet-xy-end + * + * and then in code + * ``` + * fun `ex-...`(){ + * //snippet-xy-insert + * } */ -class FirstExampleSpec: Spek({ - - fun expect(t: T): Expect = readme.examples.utils.expect(t) - - test("ex-first") { - //snippet-import-insert - - val x = 10 - expect(x).toEqual(9) - } -}) +@ExtendWith(ReadmeInvocationInterceptor::class) +@Order(0) +interface ReadmeTest diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTestClassOrderer.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTestClassOrderer.kt new file mode 100644 index 0000000000..b0d7588d8b --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/ReadmeTestClassOrderer.kt @@ -0,0 +1,17 @@ +package readme.examples.jupiter + +import org.junit.jupiter.api.ClassDescriptor +import org.junit.jupiter.api.ClassOrderer +import org.junit.jupiter.api.ClassOrdererContext + + +class ReadmeTestClassOrderer : ClassOrderer { + override fun orderClasses(context: ClassOrdererContext) { + val index = context.classDescriptors.indexOfFirst { it.testClass == WriteReadmeTest::class.java } + if (index >= 0) { + val descriptor = context.classDescriptors.removeAt(index) + @Suppress("UNCHECKED_CAST") + (context.classDescriptors as MutableList).add(descriptor) + } + } +} diff --git a/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/WriteReadmeTest.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/WriteReadmeTest.kt new file mode 100644 index 0000000000..0d9617d896 --- /dev/null +++ b/misc/tools/readme-examples/src/test/kotlin/readme/examples/jupiter/WriteReadmeTest.kt @@ -0,0 +1,218 @@ +package readme.examples.jupiter + +import ch.tutteli.kbox.failIf +import ch.tutteli.niok.absolutePathAsString +import ch.tutteli.niok.exists +import ch.tutteli.niok.readText +import ch.tutteli.niok.writeText +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import java.nio.file.Paths +import kotlin.test.fail + +class WriteReadmeTest { + + @Test + fun updateReadme() { + check(ReadmeState.code.isNotEmpty()) { + "no code collected, did you run WriteReadmeTest without other tests?" + } + check(ReadmeState.examples.isNotEmpty()) { + "no examples collected, did you run WriteReadmeTest without other tests?" + } + + val specContents = ReadmeState.testClasses + .asSequence() + .map { it.name.replace('.', '/') } + .map { qualifiedClass -> + qualifiedClass to fileContent("src/test/kotlin/$qualifiedClass.kt") + } + .toList() + specContents.forEach { (qualifiedClass, specContent) -> + extractSnippets(qualifiedClass, specContent) + } + + check(ReadmeState.snippets.isNotEmpty()) { + "no snippets found, something is wrong" + } + var readmeContent = fileContent(README_PATH) + + ReadmeState.examples.forEach { (exampleId, output) -> + readmeContent = updateExampleLikeInReadme(readmeContent, specContents, exampleId, output) + } + ReadmeState.code.forEach { codeId -> + readmeContent = updateCodeInReadme(readmeContent, specContents, codeId) + } + ReadmeState.snippets.forEach { (snippetId, snippetContent) -> + readmeContent = updateSnippetInReadme(readmeContent, snippetId, snippetContent) + } + Paths.get(README_PATH).writeText(readmeContent) + } + + + private fun fileContent(path: String): String { + val file = Paths.get(path) + check(file.exists) { "could not find ${file.absolutePathAsString}" } + return file.readText() + } + + + private fun extractSnippets(qualifiedClass: String, specContent: String) { + Regex("//(snippet-.+)-start([\\S\\s]*?)//(snippet-.+)-end").findAll(specContent).forEach { + val (tag, content, endTag) = it.destructured + failIf(tag != endTag) { "tag $tag-start did not end with $tag-end but with $endTag" } + failIf(ReadmeState.snippets.containsKey(tag)) { "snippet $tag already defined, found a second time in $qualifiedClass" } + ReadmeState.snippets[tag] = content.trimIndent() + } + } + + + private fun updateExampleLikeInReadme( + readmeContent: String, + specContents: List>, + exampleId: String, + exampleOutput: String, + ): String = when { + exampleId.startsWith("ex-") -> + updateExampleInReadme(readmeContent, specContents, exampleId, exampleOutput) + + exampleId.startsWith("exs-") -> + updateSplitExampleInReadme(readmeContent, specContents, exampleId, exampleOutput) + + else -> { + fail("unknown example kind $exampleId") + } + } + + + private fun updateExampleInReadme( + readmeContent: String, + specContents: List>, + exampleId: String, + exampleOutput: String, + ): String = updateTagInReadme( + readmeContent, + specContents, + exampleId, + "example" + ) { qualifiedClass, lineNumber, sourceCode -> + """```kotlin + |$sourceCode + |``` + |↑ [Example](https://github.com/robstoll/atrium/${System.getenv("README_SOURCETREE")}/misc/tools/readme-examples/src/main/kotlin/$qualifiedClass.kt#L$lineNumber)[Output](#$exampleId) + | + |```text + |$exampleOutput + |``` + """.trimMargin() + } + + + private fun updateSplitExampleInReadme( + readmeContent: String, + specContents: List>, + exampleId: String, + exampleOutput: String, + ): String { + val updatedReadme = updateTagInReadme( + readmeContent, specContents, exampleId, "ex-code" + ) { _, _, sourceCode -> + """```kotlin + |$sourceCode + |```""".trimMargin() + } + + return updateTagInReadme(updatedReadme, "$exampleId-output", "exs-output") { + """```text + |$exampleOutput + |```""".trimMargin() + } + } + + private fun updateCodeInReadme( + readmeContent: String, + specContents: List>, + snippetId: String, + ): String = updateTagInReadme(readmeContent, specContents, snippetId, "code") { _, _, sourceCode -> + + """```kotlin + |$sourceCode + |``` + """.trimMargin() + } + + + private fun updateTagInReadme( + readmeContent: String, + specContents: List>, + tag: String, + kind: String, + content: (String, Int, String) -> String + ): String = updateTagInReadme(readmeContent, tag, kind) { + val (qualifiedClass, lineNumber, sourceCode) = extractSourceCode(specContents, tag) + content(qualifiedClass, lineNumber, sourceCode) + } + + private fun extractSourceCode( + specContents: List>, + testId: String, + ): Triple { + var lineNumber: Int? = null + val sb = StringBuilder() + + specContents.forEach { (qualifiedClass, specContent) -> + specContent.lineSequence().forEachIndexed { index, line -> + if (lineNumber != null) { + if (line.startsWith(" }")) return Triple( + qualifiedClass, + lineNumber!!, + sb.toString().trimIndent() + ) + sb.append(line).append("\n") + + } else if (line.trim().startsWith("fun `$testId`")) { + lineNumber = index + 1 + } + } + } + fail("cannot find source code for $testId") + } + + private fun updateTagInReadme( + readmeContent: String, + tag: String, + kind: String, + contentProvider: () -> String + ): String { + val tagRegex = Regex("( *)<$tag>[\\S\\s]*") + return if (!tagRegex.containsMatchIn(readmeContent)) { + fail("$kind tag <$tag> not found in $README_PATH") + } else { + tagRegex.replace(readmeContent) { + """<$tag> + | + |${contentProvider()} + | + """.trimMargin().prependIndent(it.destructured.component1()) + } + } + } + + private fun updateSnippetInReadme( + readmeContent: String, + snippetId: String, + snippetContent: String, + ): String { + + val snippet = Regex("//$snippetId-insert") + return if (!snippet.containsMatchIn(readmeContent)) { + fail("$snippetId is never inserted in $README_PATH") + } else { + snippet.replace(readmeContent) { snippetContent } + } + } + + companion object { + private const val README_PATH = "../../../README.md" + } +} diff --git a/misc/tools/readme-examples/src/main/kotlin/readme/examples/utils/expect.kt b/misc/tools/readme-examples/src/test/kotlin/readme/examples/utils/expect.kt similarity index 100% rename from misc/tools/readme-examples/src/main/kotlin/readme/examples/utils/expect.kt rename to misc/tools/readme-examples/src/test/kotlin/readme/examples/utils/expect.kt diff --git a/misc/tools/readme-examples/src/test/resources/junit-platform.properties b/misc/tools/readme-examples/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..036885f7dc --- /dev/null +++ b/misc/tools/readme-examples/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testclass.order.default=readme.examples.jupiter.ReadmeTestClassOrderer