From dd3c53a0db67e97dcebb5a5a31156ddc2da2d8ba Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Tue, 30 Aug 2022 22:05:56 +0200
Subject: [PATCH 01/18] Add doc for writing/reading table data to a file #3179
Readme-oriented programming to make sure the proposed API make sense
---
.../assertions/table_driven_testing.md | 90 +++++++++++++++++++
1 file changed, 90 insertions(+)
create mode 100644 documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
diff --git a/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
new file mode 100644
index 00000000000..a3bbf70fea5
--- /dev/null
+++ b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
@@ -0,0 +1,90 @@
+# Table-driven testing
+
+> TK: what current exists is not really documented anywhere, am I right?
+
+## Define a table in code
+
+You can define a table of data that will be used for your test like this:
+
+```kotlin
+val table = table(
+ headers("id", "name", "username"),
+ row(4, "Jean-Michel Fayard", "jmfayard"),
+ row(6, "Louis CAD", "LouisCAD"),
+)
+```
+
+It's now easy to run your asserts for all rows of the table:
+
+## Run asserts forAll rows of the table
+
+```kotlin
+test("table-driven testing") {
+ table.forAll { id, name, username ->
+ id shouldBeGreaterThan 0
+ username shouldNotBe ""
+ }
+}
+```
+
+The test will not fail at the first error. Instead, it will always run on all the rows, and report multiple errors if they are present.
+
+Defining a table of data in code is convenient... until you start to have too much rows.
+
+## Export a table to a text file
+
+You can export your data to a text file with the `.table` extension.
+
+```kotlin
+val tableFile = testResources.resolve("users.table")
+table.writeTo(tableFile)
+```
+
+The `users.table` file looks like this:
+
+```csv
+id | username | fullName
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
+```
+
+Curious why it's not just a .csv file?
+
+Well CSV is not a well defined format. Everyone has its flavor and we have too. The `.table` has its rules: it always uses `|` as separator, it must have an header, it can have comments, it can't have newlines inside the columns. Basically it's optimized for putting table data in a `.table` file.
+
+We hope you don't use Microsoft Excel to edit the CSV-like file. IntelliJ with the [CSV plugin from Martin Sommer](https://plugins.jetbrains.com/plugin/10037-csv) does that better. You can associate the `.table` extension with it and configure `|` as your CSV separator. It has a table edition mode too!
+
+Now that your table data lives in a file, it's time to read it!
+
+## Read table from files and execute your asserts
+
+Here is how you read your `.table` file:
+
+```kotlin
+val tableFromFile = table(
+ source = testResources.resolve("users.table"),
+ headers = headers("id", "username", "fullName"),
+ transform = { a: String, b: String, c: String ->
+ row(a.toInt(), b, c)
+ }
+)
+```
+
+The arguments are:
+- the file where your table is stored
+- the same headers as before: `headers("id", "username", "fullName")`
+- a lambda to convert from strings (everything is a string in the text file) to the typed row you had before
+
+
+The rest works just like before:
+
+```kotlin
+test("table-driven testing from the .table file") {
+ // asserts like before
+ tableFromFile.forAll { id, name, username ->
+ id shouldBeGreaterThan 0
+ username shouldNotBe ""
+ }
+}
+```
+
From d47b0278157704bb88525c416e9ff518d57e520f Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 15:15:00 +0200
Subject: [PATCH 02/18] =?UTF-8?q?=E2=9C=A8table(headers3,fileContent)=20+?=
=?UTF-8?q?=20StringTable?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
All rows must have the right number of columns
---
.../assertions/table_driven_testing.md | 3 +-
.../sksamuel/kotest/data/TableParsingTest.kt | 40 ++++++++++++++++++
.../kotlin/io/kotest/data/TableParsing.kt | 41 +++++++++++++++++++
3 files changed, 82 insertions(+), 2 deletions(-)
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
create mode 100644 kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
diff --git a/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
index a3bbf70fea5..f10ba9fd137 100644
--- a/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
+++ b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
@@ -62,8 +62,8 @@ Here is how you read your `.table` file:
```kotlin
val tableFromFile = table(
- source = testResources.resolve("users.table"),
headers = headers("id", "username", "fullName"),
+ source = testResources.resolve("users.table"),
transform = { a: String, b: String, c: String ->
row(a.toInt(), b, c)
}
@@ -87,4 +87,3 @@ test("table-driven testing from the .table file") {
}
}
```
-
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
new file mode 100644
index 00000000000..bc05f496ca3
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
@@ -0,0 +1,40 @@
+package com.sksamuel.kotest.data
+
+import io.kotest.assertions.throwables.shouldThrowMessage
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.data.headers
+import io.kotest.data.row
+import io.kotest.data.table
+
+class TableParsingTest : FunSpec({
+
+ val headers = headers("id", "username", "fullName")
+ val headersText = "id | username | fullName"
+ val transform = { a: String, b: String, c: String ->
+ row(a.toInt(), b, c)
+ }
+ val expectedTable = table(
+ headers,
+ row(4, "jmfayard", "Jean-Michel Fayard"),
+ row(6, "louis", "Louis Caugnault"),
+ )
+
+ val validFileContent = """
+ 4 | jmfayard | Jean-Michel Fayard
+ 6 | louis | Louis Caugnault
+ """.trimIndent()
+
+
+ test("All rows must have the right number of columns") {
+ val invalidRows = """
+ 4 | jmfayard | Jean-Michel Fayard
+ 5 | victor | Victor Hugo | victor.hugo@guernesey.co.k
+ 6 | louis | Louis Caugnault
+ 7 | edgar
+ """.trimIndent()
+ shouldThrowMessage("Expected all rows to have size 3, but got rows at lines [1, 3]") {
+ table(headers, invalidRows, transform)
+ }
+ }
+
+})
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
new file mode 100644
index 00000000000..90172b12c03
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
@@ -0,0 +1,41 @@
+package io.kotest.data
+
+import io.kotest.assertions.fail
+import io.kotest.matchers.shouldBe
+
+// TODO I'm only supporting table with 3 arguments until the API stabilizes
+
+fun table(
+ headers: Headers3,
+ fileContent: String,
+ transform: (String, String, String) -> Row3
+): Table3 = parseTableContent(headers.values(), fileContent).let { matrix ->
+ val rows = matrix.map { row -> transform(row[0], row[1], row[2]) }
+ table(headers, *rows.toTypedArray())
+}
+
+internal fun parseTableContent(headers: List, fileContent: String) : List> {
+ val table = StringTable(headers, fileContent.lines())
+ return emptyList()
+}
+
+internal data class StringTable(
+ val headers: List,
+ val lines: List,
+) {
+ val rows: List> = lines.map(this::parseRow)
+ init {
+ rowsShouldHaveSize(headers.size)
+ }
+
+ private fun rowsShouldHaveSize(size: Int) {
+ val invalid = rows.withIndex()
+ .filter { it.value.size != size }
+ .map { it.index }
+ if (invalid.isNotEmpty()) fail("Expected all rows to have size $size, but got rows at lines $invalid")
+ }
+
+ private fun parseRow(value: String): List =
+ value.split("|")
+ .map(String::trim)
+}
From 66c006219b61bd96aabc0f70cb0b625d212cb9b6 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 15:23:02 +0200
Subject: [PATCH 03/18] =?UTF-8?q?=E2=9C=A8table=20happy=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/TableParsingTest.kt | 4 ++++
.../kotlin/io/kotest/data/TableParsing.kt | 20 ++++++++-----------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
index bc05f496ca3..46cc5f34a1a 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
@@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
+import io.kotest.matchers.shouldBe
class TableParsingTest : FunSpec({
@@ -24,6 +25,9 @@ class TableParsingTest : FunSpec({
6 | louis | Louis Caugnault
""".trimIndent()
+ test("happy path") {
+ table(headers, validFileContent, transform) shouldBe expectedTable
+ }
test("All rows must have the right number of columns") {
val invalidRows = """
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
index 90172b12c03..a56e6cf5cde 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
@@ -1,7 +1,6 @@
package io.kotest.data
import io.kotest.assertions.fail
-import io.kotest.matchers.shouldBe
// TODO I'm only supporting table with 3 arguments until the API stabilizes
@@ -9,24 +8,21 @@ fun table(
headers: Headers3,
fileContent: String,
transform: (String, String, String) -> Row3
-): Table3 = parseTableContent(headers.values(), fileContent).let { matrix ->
- val rows = matrix.map { row -> transform(row[0], row[1], row[2]) }
- table(headers, *rows.toTypedArray())
-}
-
-internal fun parseTableContent(headers: List, fileContent: String) : List> {
- val table = StringTable(headers, fileContent.lines())
- return emptyList()
+): Table3 {
+ val table = StringTable(headers.values(), fileContent.lines())
+ val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
+ return Table3(headers, rows)
}
internal data class StringTable(
val headers: List,
val lines: List,
) {
+ fun mapRows(fn: (List) -> T): List =
+ rows.map(fn)
+
val rows: List> = lines.map(this::parseRow)
- init {
- rowsShouldHaveSize(headers.size)
- }
+ init { rowsShouldHaveSize(headers.size) }
private fun rowsShouldHaveSize(size: Int) {
val invalid = rows.withIndex()
From db316098af529f02fea7bd4558c83dd347dc4d71 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 15:53:59 +0200
Subject: [PATCH 04/18] =?UTF-8?q?=F0=9F=A5=9Atable:=20'|'=20can=20be=20esc?=
=?UTF-8?q?aped=20with=20'\|'?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/TableParsingTest.kt | 23 +++++++++++++++++
.../kotlin/io/kotest/data/TableParsing.kt | 25 ++++++++++++++-----
2 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
index 46cc5f34a1a..f75c9ff0b58 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
@@ -29,6 +29,19 @@ class TableParsingTest : FunSpec({
table(headers, validFileContent, transform) shouldBe expectedTable
}
+ test("empty lines and comments starting with # are accepted") {
+ val fileContent = """
+ |4 | jmfayard | Jean-Michel Fayard
+ |# this is a comment
+ |# newlines are allowed
+ |
+ |6 | louis | Louis Caugnault
+ """.trimMargin()
+ val table = table(headers, fileContent, transform)
+
+ table shouldBe expectedTable
+ }
+
test("All rows must have the right number of columns") {
val invalidRows = """
4 | jmfayard | Jean-Michel Fayard
@@ -41,4 +54,14 @@ class TableParsingTest : FunSpec({
}
}
+ test("The '|' character can be escaped") {
+ val fileContent = """
+ 1 | bad \| good | name
+ """.trimIndent()
+ table(headers, fileContent, transform) shouldBe table(
+ headers,
+ row(1, "bad | good", "name")
+ )
+ }
+
})
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
index a56e6cf5cde..e49fa38d9d1 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
@@ -19,19 +19,32 @@ internal data class StringTable(
val lines: List,
) {
fun mapRows(fn: (List) -> T): List =
- rows.map(fn)
+ rows.map { fn(it.value) }
+
+ val rows: List>> =
+ lines
+ .withIndex()
+ .filterNot { (_, line) ->
+ line.startsWith("#") || line.isBlank()
+ }
+ .map { it.parseRow() }
- val rows: List> = lines.map(this::parseRow)
init { rowsShouldHaveSize(headers.size) }
private fun rowsShouldHaveSize(size: Int) {
- val invalid = rows.withIndex()
+ val invalid = rows
.filter { it.value.size != size }
.map { it.index }
if (invalid.isNotEmpty()) fail("Expected all rows to have size $size, but got rows at lines $invalid")
}
- private fun parseRow(value: String): List =
- value.split("|")
- .map(String::trim)
+ private fun IndexedValue.parseRow(): IndexedValue> {
+ val (index, line) = this
+ val notAPipeSeparator = "🫓"
+ return line
+ .replace("\\|", notAPipeSeparator)
+ .split("|")
+ .map { it.trim().replace(notAPipeSeparator, "|") }
+ .let { IndexedValue(index, it) }
+ }
}
From f99ed8f24b1399a11d241df872dc086f45b11f7d Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 15:55:07 +0200
Subject: [PATCH 05/18] =?UTF-8?q?=E2=99=BB=EF=B8=8Fmove=20to=20StringTable?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../{TableParsingTest.kt => StringTableTest.kt} | 3 +--
.../data/{TableParsing.kt => StringTable.kt} | 12 ------------
.../src/commonMain/kotlin/io/kotest/data/tables.kt | 12 ++++++++++++
.../src/jvmMain/kotlin/io/kotest/data/TableFile.kt | 14 ++++++++++++++
4 files changed, 27 insertions(+), 14 deletions(-)
rename kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/{TableParsingTest.kt => StringTableTest.kt} (95%)
rename kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/{TableParsing.kt => StringTable.kt} (73%)
create mode 100644 kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
similarity index 95%
rename from kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
rename to kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index f75c9ff0b58..99e6b44ab3b 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/TableParsingTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -7,10 +7,9 @@ import io.kotest.data.row
import io.kotest.data.table
import io.kotest.matchers.shouldBe
-class TableParsingTest : FunSpec({
+class StringTableTest : FunSpec({
val headers = headers("id", "username", "fullName")
- val headersText = "id | username | fullName"
val transform = { a: String, b: String, c: String ->
row(a.toInt(), b, c)
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
similarity index 73%
rename from kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
rename to kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index e49fa38d9d1..1819a3020a5 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/TableParsing.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -2,18 +2,6 @@ package io.kotest.data
import io.kotest.assertions.fail
-// TODO I'm only supporting table with 3 arguments until the API stabilizes
-
-fun table(
- headers: Headers3,
- fileContent: String,
- transform: (String, String, String) -> Row3
-): Table3 {
- val table = StringTable(headers.values(), fileContent.lines())
- val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
- return Table3(headers, rows)
-}
-
internal data class StringTable(
val headers: List,
val lines: List,
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
index fce13e882f2..bc7a479b084 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
@@ -45,3 +45,15 @@ data class Table19(val headers: Headers20, val rows: List>)
data class Table21(val headers: Headers21, val rows: List>)
data class Table22(val headers: Headers22, val rows: List>)
+
+// TODO I'm only supporting table with 3 arguments until the API stabilizes
+
+fun table(
+ headers: Headers3,
+ fileContent: String,
+ transform: (String, String, String) -> Row3
+): Table3 {
+ val table = StringTable(headers.values(), fileContent.lines())
+ val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
+ return Table3(headers, rows)
+}
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
new file mode 100644
index 00000000000..d4b9141f3c7
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -0,0 +1,14 @@
+package io.kotest.data
+
+// TODO I'm only supporting table with 3 arguments until the API stabilizes
+
+fun table(
+ headers: Headers3,
+ fileContent: String,
+ transform: (String, String, String) -> Row3
+): Table3 {
+ val table = StringTable(headers.values(), fileContent.lines())
+ val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
+ return Table3(headers, rows)
+}
+
From ac131a4230fa0377833e39e1d583e92107595810 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 16:32:22 +0200
Subject: [PATCH 06/18] =?UTF-8?q?=E2=9C=A8validating=20table=20files?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 34 +++++++++++++++++++
.../resources/table/users-invalid-empty.table | 0
.../table/users-invalid-extension.csv | 3 ++
.../table/users-invalid-header.table | 3 ++
.../jvmTest/resources/table/users-valid.table | 3 ++
.../kotlin/io/kotest/data/StringTable.kt | 24 ++++++++-----
.../kotlin/io/kotest/data/TableFile.kt | 17 ++++++++--
7 files changed, 73 insertions(+), 11 deletions(-)
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-empty.table
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-extension.csv
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-header.table
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-valid.table
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 99e6b44ab3b..7e992da4dcb 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -6,6 +6,7 @@ import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
import io.kotest.matchers.shouldBe
+import java.io.File
class StringTableTest : FunSpec({
@@ -63,4 +64,37 @@ class StringTableTest : FunSpec({
)
}
+ test("happy path for reading a table from a file") {
+
+ }
+
+ test("Validating table files") {
+ val relative = File("src/jvmTest/resources/table")
+
+ shouldThrowMessage("Can't read table file") {
+ val file = relative.resolve("users-does-not-exist.table")
+ table(headers, file, transform)
+ }
+
+ shouldThrowMessage("Table file must have a .table extension") {
+ val file = relative.resolve("users-invalid-extension.csv")
+ table(headers, file, transform)
+ }
+
+ shouldThrowMessage("Table file must have a header") {
+ val file = relative.resolve("users-invalid-empty.table")
+ table(headers, file, transform)
+ }
+
+ shouldThrowMessage(
+ """
+ Missing elements from index 2
+ expected:<["id", "username", "fullName"]> but was:<["id", "username"]>
+ """.trimIndent()
+ ) {
+ val file = relative.resolve("users-invalid-header.table")
+ table(headers, file, transform)
+ }
+ }
+
})
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-empty.table b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-empty.table
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-extension.csv b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-extension.csv
new file mode 100644
index 00000000000..b50ed0bc3ef
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-extension.csv
@@ -0,0 +1,3 @@
+id | username | fullName
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-header.table b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-header.table
new file mode 100644
index 00000000000..fe7bf6629ae
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-invalid-header.table
@@ -0,0 +1,3 @@
+id | username
+4 | jmfayard
+6 | louis
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-valid.table b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-valid.table
new file mode 100644
index 00000000000..b50ed0bc3ef
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/users-valid.table
@@ -0,0 +1,3 @@
+id | username | fullName
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index 1819a3020a5..f7b10330c39 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -5,15 +5,18 @@ import io.kotest.assertions.fail
internal data class StringTable(
val headers: List,
val lines: List,
+ val skipFirstLine: Boolean = false, // for files
) {
+
fun mapRows(fn: (List) -> T): List =
rows.map { fn(it.value) }
val rows: List>> =
lines
.withIndex()
- .filterNot { (_, line) ->
- line.startsWith("#") || line.isBlank()
+ .filterNot { (index, line) ->
+ val skipHeader = index == 0 && skipFirstLine
+ skipHeader || line.startsWith("#") || line.isBlank()
}
.map { it.parseRow() }
@@ -28,11 +31,16 @@ internal data class StringTable(
private fun IndexedValue.parseRow(): IndexedValue> {
val (index, line) = this
- val notAPipeSeparator = "🫓"
- return line
- .replace("\\|", notAPipeSeparator)
- .split("|")
- .map { it.trim().replace(notAPipeSeparator, "|") }
- .let { IndexedValue(index, it) }
+ return IndexedValue(index, Companion.parseRow(line))
+ }
+
+ companion object {
+ internal fun parseRow(line: String): List {
+ val notAPipeSeparator = "🫓"
+ return line
+ .replace("\\|", notAPipeSeparator)
+ .split("|")
+ .map { it.trim().replace(notAPipeSeparator, "|") }
+ }
}
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index d4b9141f3c7..9a5e1fb8f3e 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -1,14 +1,25 @@
package io.kotest.data
+import io.kotest.matchers.shouldBe
+import java.io.File
+
// TODO I'm only supporting table with 3 arguments until the API stabilizes
fun table(
headers: Headers3,
- fileContent: String,
+ file: File,
transform: (String, String, String) -> Row3
): Table3 {
- val table = StringTable(headers.values(), fileContent.lines())
- val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
+ val rows = file.toStringTable(headers.values())
+ .mapRows { (a, b, c) -> transform(a, b, c) }
return Table3(headers, rows)
}
+internal fun File.toStringTable(headers: List): StringTable {
+ if (exists().not()) throw AssertionError("Can't read table file")
+ if (extension != "table") throw AssertionError("Table file must have a .table extension")
+ val lines = readLines()
+ if (lines.isEmpty()) throw AssertionError("Table file must have a header")
+ StringTable.parseRow(lines.first()) shouldBe headers
+ return StringTable(headers, lines, skipFirstLine = true)
+}
From afb5e6cdf2b19b6ac204a1801fe60e22111dc11c Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 16:34:18 +0200
Subject: [PATCH 07/18] =?UTF-8?q?=E2=9C=A8table:=20reading=20from=20a=20fi?=
=?UTF-8?q?le?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/sksamuel/kotest/data/StringTableTest.kt | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 7e992da4dcb..3c141736092 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -64,25 +64,27 @@ class StringTableTest : FunSpec({
)
}
- test("happy path for reading a table from a file") {
+ val resourcesDir = File("src/jvmTest/resources/table")
+ test("happy path for reading a table from a file") {
+ val file = resourcesDir.resolve("users-valid.table")
+ table(headers, file, transform) shouldBe expectedTable
}
test("Validating table files") {
- val relative = File("src/jvmTest/resources/table")
shouldThrowMessage("Can't read table file") {
- val file = relative.resolve("users-does-not-exist.table")
+ val file = resourcesDir.resolve("users-does-not-exist.table")
table(headers, file, transform)
}
shouldThrowMessage("Table file must have a .table extension") {
- val file = relative.resolve("users-invalid-extension.csv")
+ val file = resourcesDir.resolve("users-invalid-extension.csv")
table(headers, file, transform)
}
shouldThrowMessage("Table file must have a header") {
- val file = relative.resolve("users-invalid-empty.table")
+ val file = resourcesDir.resolve("users-invalid-empty.table")
table(headers, file, transform)
}
@@ -92,7 +94,7 @@ class StringTableTest : FunSpec({
expected:<["id", "username", "fullName"]> but was:<["id", "username"]>
""".trimIndent()
) {
- val file = relative.resolve("users-invalid-header.table")
+ val file = resourcesDir.resolve("users-invalid-header.table")
table(headers, file, transform)
}
}
From 722d3d02106f49c54f4be7625a2e8a84fa9e98c0 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 17:27:00 +0200
Subject: [PATCH 08/18] =?UTF-8?q?=E2=9C=A8table:=20table.writeToFile()=20h?=
=?UTF-8?q?appy=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 10 +++++++++
.../jvmTest/resources/table/writeToFile.table | 3 +++
.../kotlin/io/kotest/data/StringTable.kt | 11 ++++------
.../kotlin/io/kotest/data/TableFile.kt | 22 +++++++++++++++++--
4 files changed, 37 insertions(+), 9 deletions(-)
create mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 3c141736092..1568681c769 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
+import io.kotest.data.writeToFile
import io.kotest.matchers.shouldBe
import java.io.File
@@ -99,4 +100,13 @@ class StringTableTest : FunSpec({
}
}
+ test("table.writeToFile() - happy path") {
+ val file = resourcesDir.resolve("writeToFile.table")
+ expectedTable.writeToFile(file)
+ file.readText() shouldBe """
+id | username | fullName
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
+ """.trim()
+ }
})
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
new file mode 100644
index 00000000000..91f8c5bb61b
--- /dev/null
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
@@ -0,0 +1,3 @@
+id | username | fullName
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
\ No newline at end of file
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index f7b10330c39..2b370653d1d 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -18,9 +18,11 @@ internal data class StringTable(
val skipHeader = index == 0 && skipFirstLine
skipHeader || line.startsWith("#") || line.isBlank()
}
- .map { it.parseRow() }
+ .map { (index, line) -> IndexedValue(index, parseRow(line)) }
- init { rowsShouldHaveSize(headers.size) }
+ init {
+ rowsShouldHaveSize(headers.size)
+ }
private fun rowsShouldHaveSize(size: Int) {
val invalid = rows
@@ -29,11 +31,6 @@ internal data class StringTable(
if (invalid.isNotEmpty()) fail("Expected all rows to have size $size, but got rows at lines $invalid")
}
- private fun IndexedValue.parseRow(): IndexedValue> {
- val (index, line) = this
- return IndexedValue(index, Companion.parseRow(line))
- }
-
companion object {
internal fun parseRow(line: String): List {
val notAPipeSeparator = "🫓"
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index 9a5e1fb8f3e..811a8ca0160 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -10,12 +10,17 @@ fun table(
file: File,
transform: (String, String, String) -> Row3
): Table3 {
- val rows = file.toStringTable(headers.values())
+ val rows = file.readStringTable(headers.values())
.mapRows { (a, b, c) -> transform(a, b, c) }
return Table3(headers, rows)
}
-internal fun File.toStringTable(headers: List): StringTable {
+fun Table3.writeToFile(file: File) {
+ val cells = rows.map { row -> row.values().map { it.toString() } }
+ writeToFile(file, headers.values(), cells)
+}
+
+internal fun File.readStringTable(headers: List): StringTable {
if (exists().not()) throw AssertionError("Can't read table file")
if (extension != "table") throw AssertionError("Table file must have a .table extension")
val lines = readLines()
@@ -23,3 +28,16 @@ internal fun File.toStringTable(headers: List): StringTable {
StringTable.parseRow(lines.first()) shouldBe headers
return StringTable(headers, lines, skipFirstLine = true)
}
+
+fun writeToFile(file: File, headers: List, cells: List>) {
+ val separator = " | "
+ val formattedHeader = headers.joinToString(separator)
+ val formattedContent = cells.joinToString("\n") { row ->
+ row.joinToString(separator)
+ }
+
+ file.writeText("""
+$formattedHeader
+$formattedContent
+ """.trimIndent())
+}
From 9bb9e315665c09d0a93d5fbf1353227b9a7fe819 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 22:32:49 +0200
Subject: [PATCH 09/18] =?UTF-8?q?=E2=9C=A8map.toTable()?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/sksamuel/kotest/data/StringTableTest.kt | 13 +++++++++++++
.../src/commonMain/kotlin/io/kotest/data/tables.kt | 5 +++++
2 files changed, 18 insertions(+)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 1568681c769..dda3237e205 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -5,6 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
+import io.kotest.data.toTable
import io.kotest.data.writeToFile
import io.kotest.matchers.shouldBe
import java.io.File
@@ -26,6 +27,18 @@ class StringTableTest : FunSpec({
6 | louis | Louis Caugnault
""".trimIndent()
+ test("create table from map") {
+ val map = mapOf(
+ "fr" to "French",
+ "es" to "Spanish"
+ )
+ val table = map.toTable(headers("code", "language"))
+ table shouldBe table(
+ headers("code", "language"),
+ row("fr", "French"),
+ row("es", "Spanish"),
+ )
+
test("happy path") {
table(headers, validFileContent, transform) shouldBe expectedTable
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
index bc7a479b084..86c1e9ba256 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
@@ -1,5 +1,10 @@
package io.kotest.data
+fun Map.toTable(
+ headers: Headers2 = headers("key", "value"),
+) = Table2(headers, entries.map { row(it.key, it.value) })
+
+
fun table(headers: Headers1, vararg rows: Row1) = Table1(headers, rows.asList())
fun table(headers: Headers2, vararg rows: Row2) = Table2(headers, rows.asList())
fun table(headers: Headers3, vararg rows: Row3) = Table3(headers, rows.asList())
From d376d4f8bb6b8e5e9b006c35ab15a3718a12040a Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 22:43:33 +0200
Subject: [PATCH 10/18] =?UTF-8?q?=E2=9C=A8table(headers(...),=20list.map?=
=?UTF-8?q?=20{=20row(...)=20})?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 43 ++++++++++++++-----
.../kotlin/io/kotest/data/tables.kt | 3 ++
2 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index dda3237e205..def4bccc55a 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -27,17 +27,38 @@ class StringTableTest : FunSpec({
6 | louis | Louis Caugnault
""".trimIndent()
- test("create table from map") {
- val map = mapOf(
- "fr" to "French",
- "es" to "Spanish"
- )
- val table = map.toTable(headers("code", "language"))
- table shouldBe table(
- headers("code", "language"),
- row("fr", "French"),
- row("es", "Spanish"),
- )
+ context("creating tables from collections") {
+ test("create table from map") {
+ val map = mapOf(
+ "fr" to "French",
+ "es" to "Spanish"
+ )
+ val table = map.toTable(headers("code", "language"))
+ table shouldBe table(
+ headers("code", "language"),
+ row("fr", "French"),
+ row("es", "Spanish"),
+ )
+ }
+
+ test("create table from list") {
+ data class Language(val code: String, val english: String, val name: String)
+
+ val languages = listOf(
+ Language("fr", "French", "Français"),
+ Language("es", "Spanish", "Español"),
+ )
+ val table = table(
+ headers("code", "name", "english"),
+ languages.map { row(it.code, it.name, it.english) }
+ )
+ table shouldBe table(
+ headers("code", "name", "english"),
+ row("fr", "Français", "French"),
+ row("es", "Español", "Spanish"),
+ )
+ }
+ }
test("happy path") {
table(headers, validFileContent, transform) shouldBe expectedTable
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
index 86c1e9ba256..bf1a9ea44e8 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
@@ -4,6 +4,9 @@ fun Map.toTable(
headers: Headers2 = headers("key", "value"),
) = Table2(headers, entries.map { row(it.key, it.value) })
+// only 3 arguments for now
+fun table(headers: Headers3, rows: List>) = Table3(headers, rows)
+
fun table(headers: Headers1, vararg rows: Row1) = Table1(headers, rows.asList())
fun table(headers: Headers2, vararg rows: Row2) = Table2(headers, rows.asList())
From c6bc19197d111988b1e6a2bac072d51d6f7ee207 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 23:27:23 +0200
Subject: [PATCH 11/18] =?UTF-8?q?=E2=9C=A8file.writeTable(headers,=20rows)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 57 ++++++++----
.../jvmTest/resources/table/writeToFile.table | 3 -
.../commonMain/kotlin/io/kotest/data/rows.kt | 92 ++++++++++---------
.../kotlin/io/kotest/data/tables.kt | 10 +-
.../kotlin/io/kotest/data/TableFile.kt | 24 +++--
5 files changed, 113 insertions(+), 73 deletions(-)
delete mode 100644 kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index def4bccc55a..935c581d9f9 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -3,10 +3,12 @@ package com.sksamuel.kotest.data
import io.kotest.assertions.throwables.shouldThrowMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.data.headers
+import io.kotest.data.mapRows
import io.kotest.data.row
import io.kotest.data.table
import io.kotest.data.toTable
-import io.kotest.data.writeToFile
+import io.kotest.data.writeTable
+import io.kotest.engine.spec.tempfile
import io.kotest.matchers.shouldBe
import java.io.File
@@ -41,22 +43,25 @@ class StringTableTest : FunSpec({
)
}
- test("create table from list") {
- data class Language(val code: String, val english: String, val name: String)
+ val languagesTable = table(
+ headers("code", "name", "english"),
+ row("fr", "Français", "French"),
+ row("es", "Español", "Spanish"),
+ )
- val languages = listOf(
- Language("fr", "French", "Français"),
- Language("es", "Spanish", "Español"),
- )
+ data class Language(val code: String, val english: String, val name: String)
+
+ val languages = listOf(
+ Language("fr", "French", "Français"),
+ Language("es", "Spanish", "Español"),
+ )
+
+ test("create table from list") {
val table = table(
headers("code", "name", "english"),
languages.map { row(it.code, it.name, it.english) }
)
- table shouldBe table(
- headers("code", "name", "english"),
- row("fr", "Français", "French"),
- row("es", "Español", "Spanish"),
- )
+ table shouldBe languagesTable
}
}
@@ -134,13 +139,31 @@ class StringTableTest : FunSpec({
}
}
- test("table.writeToFile() - happy path") {
- val file = resourcesDir.resolve("writeToFile.table")
- expectedTable.writeToFile(file)
- file.readText() shouldBe """
+ context("file.writeTable(headers, rows)") {
+ data class UserInfo(val username: String, val fullName: String)
+
+ val expectedFileContent = """
id | username | fullName
4 | jmfayard | Jean-Michel Fayard
6 | louis | Louis Caugnault
""".trim()
+
+ val table = table(
+ headers("id", "UserInfo"),
+ row(4, UserInfo("jmfayard", "Jean-Michel Fayard")),
+ row(6, UserInfo("louis", "Louis Caugnault"))
+ )
+
+ test("happy path") {
+ val file = tempfile()
+ val rows = table.mapRows { (id, userInfo) ->
+ row(id.toString(), userInfo.username, userInfo.fullName)
+ }
+ val fileContent = file.writeTable(headers("id", "username", "fullName"), rows)
+ file.readText() shouldBe expectedFileContent
+ fileContent shouldBe expectedFileContent
+ }
}
-})
+
+}
+)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table b/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
deleted file mode 100644
index 91f8c5bb61b..00000000000
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/resources/table/writeToFile.table
+++ /dev/null
@@ -1,3 +0,0 @@
-id | username | fullName
-4 | jmfayard | Jean-Michel Fayard
-6 | louis | Louis Caugnault
\ No newline at end of file
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/rows.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/rows.kt
index 7ac33b0f6be..2f76b39a81c 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/rows.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/rows.kt
@@ -23,90 +23,94 @@ fun row(a: A, b: B,
fun row(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S, t: T, u: U) = Row21(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
fun row(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P, q: Q, r: R, s: S, t: T, u: U, v: V) = Row22(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v)
-data class Row1(val a: A) {
- fun values() = listOf(a)
+interface Row {
+ fun values(): List
}
-data class Row2(val a: A, val b: B) {
- fun values() = listOf(a, b)
+data class Row1(val a: A): Row {
+ override fun values() = listOf(a)
}
-data class Row3(val a: A, val b: B, val c: C) {
- fun values() = listOf(a, b, c)
+data class Row2(val a: A, val b: B): Row {
+ override fun values() = listOf(a, b)
}
-data class Row4(val a: A, val b: B, val c: C, val d: D) {
- fun values() = listOf(a, b, c, d)
+data class Row3(val a: A, val b: B, val c: C): Row {
+ override fun values() = listOf(a, b, c)
}
-data class Row5(val a: A, val b: B, val c: C, val d: D, val e: E) {
- fun values() = listOf(a, b, c, d, e)
+data class Row4(val a: A, val b: B, val c: C, val d: D): Row {
+ override fun values(): List = listOf(a, b, c, d)
}
-data class Row6(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F) {
- fun values() = listOf(a, b, c, d, e, f)
+data class Row5(val a: A, val b: B, val c: C, val d: D, val e: E): Row {
+ override fun values() = listOf(a, b, c, d, e)
}
-data class Row7(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G) {
- fun values() = listOf(a, b, c, d, e, f, g)
+data class Row6(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F): Row {
+ override fun values() = listOf(a, b, c, d, e, f)
}
-data class Row8(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H) {
- fun values() = listOf(a, b, c, d, e, f, g, h)
+data class Row7(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g)
}
-data class Row9(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i)
+data class Row8(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h)
}
-data class Row10(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j)
+data class Row9(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i)
}
-data class Row11(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k)
+data class Row10(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j)
}
-data class Row12(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l)
+data class Row11(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k)
}
-data class Row13(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m)
+data class Row12(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l)
}
-data class Row14(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
+data class Row13(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m)
}
-data class Row15(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
+data class Row14(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
}
-data class Row16(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
+data class Row15(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
-data class Row17(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q)
+data class Row16(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
}
-data class Row18(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
+data class Row17(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q)
}
-data class Row19(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
+data class Row18(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
}
-data class Row20(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t)
+data class Row19(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
}
-data class Row21(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T, val u: U) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
+data class Row20(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t)
}
-data class Row22(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T, val u: U, val v: V) {
- fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v)
+data class Row21(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T, val u: U): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
+}
+
+data class Row22(val a: A, val b: B, val c: C, val d: D, val e: E, val f: F, val g: G, val h: H, val i: I, val j: J, val k: K, val l: L, val m: M, val n: N, val o: O, val p: P, val q: Q, val r: R, val s: S, val t: T, val u: U, val v: V): Row {
+ override fun values() = listOf(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v)
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
index bf1a9ea44e8..4ec4ade013a 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
@@ -2,11 +2,17 @@ package io.kotest.data
fun Map.toTable(
headers: Headers2 = headers("key", "value"),
-) = Table2(headers, entries.map { row(it.key, it.value) })
+) = table(headers, entries.map { row(it.key, it.value) })
-// only 3 arguments for now
+fun table(headers: Headers1, rows: List>) = Table1(headers, rows)
+fun table(headers: Headers2, rows: List>) = Table2(headers, rows)
fun table(headers: Headers3, rows: List>) = Table3(headers, rows)
+// TODO more
+fun Table1.mapRows(fn: (Row1) -> T): List = rows.map(fn)
+fun Table2.mapRows(fn: (Row2) -> T): List = rows.map(fn)
+fun Table3.mapRows(fn: (Row3) -> T): List = rows.map(fn)
+// TODO more
fun table(headers: Headers1, vararg rows: Row1) = Table1(headers, rows.asList())
fun table(headers: Headers2, vararg rows: Row2) = Table2(headers, rows.asList())
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index 811a8ca0160..867e605c661 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -15,10 +15,16 @@ fun table(
return Table3(headers, rows)
}
-fun Table3.writeToFile(file: File) {
- val cells = rows.map { row -> row.values().map { it.toString() } }
- writeToFile(file, headers.values(), cells)
-}
+fun File.writeTable(headers: Headers1, rows: List>): String =
+ writeTable(headers.values(), rows.map { it.strings() } )
+fun File.writeTable(headers: Headers2, rows: List>): String =
+ writeTable(headers.values(), rows.map { it.strings() } )
+fun File.writeTable(headers: Headers3, rows: List>): String =
+ writeTable(headers.values(), rows.map { it.strings() } )
+// TODO
+
+private fun Row.strings(): List = values().map { it.toString() }
+
internal fun File.readStringTable(headers: List): StringTable {
if (exists().not()) throw AssertionError("Can't read table file")
@@ -29,15 +35,19 @@ internal fun File.readStringTable(headers: List): StringTable {
return StringTable(headers, lines, skipFirstLine = true)
}
-fun writeToFile(file: File, headers: List, cells: List>) {
+fun File.writeTable(headers: List, cells: List>): String {
+ if (extension != "table") throw AssertionError("Table file must have a .table extension")
+
val separator = " | "
val formattedHeader = headers.joinToString(separator)
val formattedContent = cells.joinToString("\n") { row ->
row.joinToString(separator)
}
- file.writeText("""
+ val fileContent = """
$formattedHeader
$formattedContent
- """.trimIndent())
+ """.trimIndent()
+ writeText(fileContent)
+ return fileContent
}
From 211817e7060b0f4f9bd8721a06c4bd75cd53f6bf Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 23:40:35 +0200
Subject: [PATCH 12/18] =?UTF-8?q?=E2=9C=85file.write(headers,=20rows):=20v?=
=?UTF-8?q?alidation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 35 +++++++++++++------
.../kotlin/io/kotest/data/TableFile.kt | 3 +-
2 files changed, 26 insertions(+), 12 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 935c581d9f9..ef76daf2d6c 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -139,23 +139,22 @@ class StringTableTest : FunSpec({
}
}
- context("file.writeTable(headers, rows)") {
- data class UserInfo(val username: String, val fullName: String)
+ data class UserInfo(val username: String, val fullName: String)
+ val table = table(
+ headers("id", "UserInfo"),
+ row(4, UserInfo("jmfayard", "Jean-Michel Fayard")),
+ row(6, UserInfo("louis", "Louis Caugnault"))
+ )
+ context("file.writeTable - success") {
val expectedFileContent = """
id | username | fullName
4 | jmfayard | Jean-Michel Fayard
6 | louis | Louis Caugnault
""".trim()
- val table = table(
- headers("id", "UserInfo"),
- row(4, UserInfo("jmfayard", "Jean-Michel Fayard")),
- row(6, UserInfo("louis", "Louis Caugnault"))
- )
-
test("happy path") {
- val file = tempfile()
+ val file = tempfile(suffix = ".table")
val rows = table.mapRows { (id, userInfo) ->
row(id.toString(), userInfo.username, userInfo.fullName)
}
@@ -165,5 +164,19 @@ id | username | fullName
}
}
-}
-)
+ context("file.writeTable - validation") {
+ test("Table file must have a .table extension") {
+ shouldThrowMessage(testCase.name.testName) {
+ tempfile().writeTable(table.headers, emptyList())
+ }
+ }
+
+ test("Cells con't contain new lines") {
+ val table =
+ mapOf("1" to "one\n", "two" to "two", "three" to "three\nthree").toTable()
+ shouldThrowMessage(testCase.name.testName) {
+ tempfile(suffix = ".table").writeTable(table.headers, table.rows)
+ }
+ }
+ }
+})
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index 867e605c661..86bd99e989b 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -37,7 +37,8 @@ internal fun File.readStringTable(headers: List): StringTable {
fun File.writeTable(headers: List, cells: List>): String {
if (extension != "table") throw AssertionError("Table file must have a .table extension")
-
+ val containsNewLines = cells.any { it.any { cell -> cell.contains("\n") } }
+ if (containsNewLines) throw AssertionError("Cells con't contain new lines")
val separator = " | "
val formattedHeader = headers.joinToString(separator)
val formattedContent = cells.joinToString("\n") { row ->
From 88fab6ba23eca62f37b0da504f7a4b7e1aa6d7cd Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Wed, 31 Aug 2022 23:46:46 +0200
Subject: [PATCH 13/18] =?UTF-8?q?=E2=9C=85escape=20pipe?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 20 ++++++++++++++-----
.../kotlin/io/kotest/data/TableFile.kt | 2 +-
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index ef76daf2d6c..181cd4c1001 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -140,7 +140,7 @@ class StringTableTest : FunSpec({
}
data class UserInfo(val username: String, val fullName: String)
- val table = table(
+ val usersTable = table(
headers("id", "UserInfo"),
row(4, UserInfo("jmfayard", "Jean-Michel Fayard")),
row(6, UserInfo("louis", "Louis Caugnault"))
@@ -155,27 +155,37 @@ id | username | fullName
test("happy path") {
val file = tempfile(suffix = ".table")
- val rows = table.mapRows { (id, userInfo) ->
+ val rows = usersTable.mapRows { (id, userInfo) ->
row(id.toString(), userInfo.username, userInfo.fullName)
}
val fileContent = file.writeTable(headers("id", "username", "fullName"), rows)
file.readText() shouldBe expectedFileContent
fileContent shouldBe expectedFileContent
}
+
+ test("| should be escaped") {
+ val table = mapOf("greeting" to "Hello || world").toTable()
+ val file = tempfile(suffix = ".table")
+ file.writeTable(table.headers, table.rows) shouldBe """
+ key | value
+ greeting | Hello \|\| world
+ """.trimIndent()
+ }
}
context("file.writeTable - validation") {
test("Table file must have a .table extension") {
shouldThrowMessage(testCase.name.testName) {
- tempfile().writeTable(table.headers, emptyList())
+ val fileMissingTableExtension = tempfile()
+ fileMissingTableExtension.writeTable(usersTable.headers, emptyList())
}
}
test("Cells con't contain new lines") {
- val table =
+ val tableWithNewLines =
mapOf("1" to "one\n", "two" to "two", "three" to "three\nthree").toTable()
shouldThrowMessage(testCase.name.testName) {
- tempfile(suffix = ".table").writeTable(table.headers, table.rows)
+ tempfile(suffix = ".table").writeTable(tableWithNewLines.headers, tableWithNewLines.rows)
}
}
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index 86bd99e989b..9d5860195f0 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -42,7 +42,7 @@ fun File.writeTable(headers: List, cells: List>): String {
val separator = " | "
val formattedHeader = headers.joinToString(separator)
val formattedContent = cells.joinToString("\n") { row ->
- row.joinToString(separator)
+ row.joinToString(separator) { it.replace("|", "\\|") }
}
val fileContent = """
From 1957d2f4ea94594504720ead5e4a0aec3d97f008 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Thu, 1 Sep 2022 00:11:51 +0200
Subject: [PATCH 14/18] =?UTF-8?q?=F0=9F=8E=89=20tables=20are=20now=20align?=
=?UTF-8?q?ed?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../sksamuel/kotest/data/StringTableTest.kt | 27 ++++++++++++++++---
.../kotlin/io/kotest/data/TableFile.kt | 21 +++++++++++++--
2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 181cd4c1001..ff308b5de0d 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -2,6 +2,7 @@ package com.sksamuel.kotest.data
import io.kotest.assertions.throwables.shouldThrowMessage
import io.kotest.core.spec.style.FunSpec
+import io.kotest.data.Row3
import io.kotest.data.headers
import io.kotest.data.mapRows
import io.kotest.data.row
@@ -149,8 +150,8 @@ class StringTableTest : FunSpec({
context("file.writeTable - success") {
val expectedFileContent = """
id | username | fullName
-4 | jmfayard | Jean-Michel Fayard
-6 | louis | Louis Caugnault
+4 | jmfayard | Jean-Michel Fayard
+6 | louis | Louis Caugnault
""".trim()
test("happy path") {
@@ -163,11 +164,31 @@ id | username | fullName
fileContent shouldBe expectedFileContent
}
+ test("columns should be aligned") {
+ fun row(i: Int): Row3 {
+ val value = "$i".repeat(i)
+ return row(value, value, value)
+ }
+
+ val table = table(
+ headers("a", "b", "c"),
+ row(2),
+ row(4),
+ row(6),
+ )
+ tempfile(suffix = ".table").writeTable(table.headers, table.rows) shouldBe """
+a | b | c
+22 | 22 | 22
+4444 | 4444 | 4444
+666666 | 666666 | 666666
+ """.trimIndent()
+ }
+
test("| should be escaped") {
val table = mapOf("greeting" to "Hello || world").toTable()
val file = tempfile(suffix = ".table")
file.writeTable(table.headers, table.rows) shouldBe """
- key | value
+ key | value
greeting | Hello \|\| world
""".trimIndent()
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
index 9d5860195f0..b71a75cc716 100644
--- a/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/jvmMain/kotlin/io/kotest/data/TableFile.kt
@@ -39,10 +39,27 @@ fun File.writeTable(headers: List, cells: List>): String {
if (extension != "table") throw AssertionError("Table file must have a .table extension")
val containsNewLines = cells.any { it.any { cell -> cell.contains("\n") } }
if (containsNewLines) throw AssertionError("Cells con't contain new lines")
+
+ val columnSizes = headers.mapIndexed { index, header ->
+ val rowsSize = cells.map { it[index].length }.maxOrNull() ?: 0
+ maxOf(header.length, rowsSize)
+ }
+
+ fun String.formatCell(index: Int) =
+ this.plus(" ".repeat(maxOf(0, columnSizes[index] - length)))
+
val separator = " | "
- val formattedHeader = headers.joinToString(separator)
+
+ val formattedHeader = headers
+ .mapIndexed { index, header -> header.formatCell(index) }
+ .joinToString(separator)
+ .trimEnd()
+
val formattedContent = cells.joinToString("\n") { row ->
- row.joinToString(separator) { it.replace("|", "\\|") }
+ val list = row.mapIndexed { index, cell ->
+ cell.replace("|", "\\|").formatCell(index)
+ }
+ list.joinToString(separator).trimEnd()
}
val fileContent = """
From 524f3abce086df19c9a749f8b941717185a3db96 Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Mon, 5 Sep 2022 17:17:24 +0200
Subject: [PATCH 15/18] =?UTF-8?q?=F0=9F=91=8Cescape=20escape,=20update=20d?=
=?UTF-8?q?oc?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../assertions/table_driven_testing.md | 9 ++++++-
.../sksamuel/kotest/data/StringTableTest.kt | 8 ++++--
.../kotlin/io/kotest/data/StringTable.kt | 25 +++++++++++++++----
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
index f10ba9fd137..302aad2c6af 100644
--- a/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
+++ b/documentation/versioned_docs/version-5.4/assertions/table_driven_testing.md
@@ -50,7 +50,14 @@ id | username | fullName
Curious why it's not just a .csv file?
-Well CSV is not a well defined format. Everyone has its flavor and we have too. The `.table` has its rules: it always uses `|` as separator, it must have an header, it can have comments, it can't have newlines inside the columns. Basically it's optimized for putting table data in a `.table` file.
+Well CSV is not a well defined format. Everyone has its flavor and we have too. The `.table` has its rules:
+
+- it always uses `|` as separator
+- it must have an header
+- cells are trimmed and cannot contain new lines
+- it can have comments and blank lines
+
+Basically it's optimized for putting table data in a `.table` file.
We hope you don't use Microsoft Excel to edit the CSV-like file. IntelliJ with the [CSV plugin from Martin Sommer](https://plugins.jetbrains.com/plugin/10037-csv) does that better. You can associate the `.table` extension with it and configure `|` as your CSV separator. It has a table edition mode too!
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index ff308b5de0d..565e63d3b4c 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -97,11 +97,15 @@ class StringTableTest : FunSpec({
test("The '|' character can be escaped") {
val fileContent = """
- 1 | bad \| good | name
+ 1 | prefix\|middle\|suffix | hello\|world
+ 2 | prefix\suffix | nothing
+ 3 | prefix\\|suffix
""".trimIndent()
table(headers, fileContent, transform) shouldBe table(
headers,
- row(1, "bad | good", "name")
+ row(1, "prefix|middle|suffix", "hello|world"),
+ row(2,"prefix\\suffix", "nothing"),
+ row(3, "prefix\\", "suffix"),
)
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index 2b370653d1d..21cb7bf49ad 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -33,11 +33,26 @@ internal data class StringTable(
companion object {
internal fun parseRow(line: String): List {
- val notAPipeSeparator = "🫓"
- return line
- .replace("\\|", notAPipeSeparator)
- .split("|")
- .map { it.trim().replace(notAPipeSeparator, "|") }
+ val result = mutableListOf()
+ val list = line.split("|")
+ val needsMerge = list.withIndex().filter { (i, cell) ->
+ cell.endsWith("\\") && cell.endsWith("\\\\").not()
+ }.map { it.index }.toSet()
+
+ var current = ""
+ list.forEachIndexed { i, cell ->
+ if (i in needsMerge) {
+ current += cell
+ .removeSuffix("\\")
+ .plus("|")
+ } else {
+ result += "$current$cell"
+ .replace("\\\\", "\\")
+ .trim()
+ current = ""
+ }
+ }
+ return result
}
}
}
From 74325030e7bbb170388c61ea8e384232c50a140e Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Mon, 5 Sep 2022 17:31:10 +0200
Subject: [PATCH 16/18] =?UTF-8?q?=F0=9F=91=8Cbetter=20error=20message=20in?=
=?UTF-8?q?=20rowsShouldHaveSize(int)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/sksamuel/kotest/data/StringTableTest.kt | 11 +++++++++--
.../kotlin/io/kotest/data/StringTable.kt | 17 ++++++++++++++---
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
index 565e63d3b4c..a1decf0d6f5 100644
--- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
+++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/data/StringTableTest.kt
@@ -86,11 +86,18 @@ class StringTableTest : FunSpec({
test("All rows must have the right number of columns") {
val invalidRows = """
4 | jmfayard | Jean-Michel Fayard
- 5 | victor | Victor Hugo | victor.hugo@guernesey.co.k
+ 5 | victor | Victor Hugo | victor.hugo@guernesey.co.uk
6 | louis | Louis Caugnault
7 | edgar
""".trimIndent()
- shouldThrowMessage("Expected all rows to have size 3, but got rows at lines [1, 3]") {
+
+ val expectedMessage = """
+Expected all rows to have 3 columns, but 2 rows differed
+- Row 1 has 4 columns: [5, victor, Victor Hugo, victor.hugo@guernesey.co.uk]
+- Row 3 has 2 columns: [7, edgar]
+ """.trimIndent()
+
+ shouldThrowMessage(expectedMessage) {
table(headers, invalidRows, transform)
}
}
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index 21cb7bf49ad..95d6a3249d7 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -25,10 +25,21 @@ internal data class StringTable(
}
private fun rowsShouldHaveSize(size: Int) {
- val invalid = rows
+ val maxRows = 5
+ val invalidRows = rows
.filter { it.value.size != size }
- .map { it.index }
- if (invalid.isNotEmpty()) fail("Expected all rows to have size $size, but got rows at lines $invalid")
+ val formattedRows = invalidRows
+ .take(maxRows)
+ .joinToString("\n") { (i, row) ->
+ "- Row $i has ${row.size} columns: $row"
+ }
+ val andMore = if (invalidRows.size <= maxRows) "" else "... and ${invalidRows.size - maxRows} other rows"
+
+ if (invalidRows.isNotEmpty()) fail("""
+ |Expected all rows to have $size columns, but ${invalidRows.size} rows differed
+ |$formattedRows
+ |$andMore
+ """.trimMargin().trim())
}
companion object {
From 678ab8cc83ba9c268eb7d4dbb274f60c572866df Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Mon, 5 Sep 2022 18:02:06 +0200
Subject: [PATCH 17/18] =?UTF-8?q?=F0=9F=91=8CseparatorRegex?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../kotlin/io/kotest/data/StringTable.kt | 34 ++++++++-----------
1 file changed, 14 insertions(+), 20 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
index 95d6a3249d7..96786cd0c95 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/StringTable.kt
@@ -35,35 +35,29 @@ internal data class StringTable(
}
val andMore = if (invalidRows.size <= maxRows) "" else "... and ${invalidRows.size - maxRows} other rows"
- if (invalidRows.isNotEmpty()) fail("""
+ if (invalidRows.isNotEmpty()) fail(
+ """
|Expected all rows to have $size columns, but ${invalidRows.size} rows differed
|$formattedRows
|$andMore
- """.trimMargin().trim())
+ """.trimMargin().trim()
+ )
}
companion object {
- internal fun parseRow(line: String): List {
- val result = mutableListOf()
- val list = line.split("|")
- val needsMerge = list.withIndex().filter { (i, cell) ->
- cell.endsWith("\\") && cell.endsWith("\\\\").not()
- }.map { it.index }.toSet()
+ val separatorRegex = Regex("([\\\\]{2}|[^\\\\])\\|")
- var current = ""
- list.forEachIndexed { i, cell ->
- if (i in needsMerge) {
- current += cell
- .removeSuffix("\\")
- .plus("|")
- } else {
- result += "$current$cell"
+ internal fun parseRow(line: String): List {
+ val trimmed = line.replace(" ", "")
+ return line
+ .split(separatorRegex)
+ .map {
+ val cell = it.trim()
+ val suffix = if ("$cell\\\\|" in trimmed) "\\" else ""
+ cell.plus(suffix)
+ .replace("\\|", "|")
.replace("\\\\", "\\")
- .trim()
- current = ""
- }
}
- return result
}
}
}
From fd22ea11c19952b6a00f1426f2725fb28a4f680c Mon Sep 17 00:00:00 2001
From: Jean-Michel Fayard <459464+jmfayard@users.noreply.github.com>
Date: Mon, 5 Sep 2022 18:54:29 +0200
Subject: [PATCH 18/18] =?UTF-8?q?=E2=9C=A8up=20to=209=20generics=20for=20t?=
=?UTF-8?q?able(rows),=20table(transform)=20table.mapRows?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../kotlin/io/kotest/data/tables.kt | 78 +++++++++++++++----
1 file changed, 63 insertions(+), 15 deletions(-)
diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
index 4ec4ade013a..37596b6aeda 100644
--- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
+++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/data/tables.kt
@@ -7,12 +7,22 @@ fun Map.toTable(
fun table(headers: Headers1, rows: List>) = Table1(headers, rows)
fun table(headers: Headers2, rows: List>) = Table2(headers, rows)
fun table(headers: Headers3, rows: List>) = Table3(headers, rows)
-// TODO more
+fun table(headers: Headers4, rows: List>) = Table4(headers, rows)
+fun table(headers: Headers5, rows: List>) = Table5(headers, rows)
+fun table(headers: Headers6, rows: List>) = Table6(headers, rows)
+fun table(headers: Headers7, rows: List>) = Table7(headers, rows)
+fun table(headers: Headers8, rows: List>) = Table8(headers, rows)
+fun table(headers: Headers9, rows: List>) = Table9(headers, rows)
-fun Table1.mapRows(fn: (Row1) -> T): List = rows.map(fn)
-fun Table2.mapRows(fn: (Row2) -> T): List = rows.map(fn)
-fun Table3.mapRows(fn: (Row3) -> T): List = rows.map(fn)
-// TODO more
+fun Table1.mapRows(fn: (Row1) -> ARow): List = rows.map(fn)
+fun Table2.mapRows(fn: (Row2) -> ARow): List = rows.map(fn)
+fun Table3.mapRows(fn: (Row3) -> ARow): List = rows.map(fn)
+fun Table4.mapRows(fn: (Row4) -> ARow): List = rows.map(fn)
+fun Table5.mapRows(fn: (Row5) -> ARow): List = rows.map(fn)
+fun Table6.mapRows(fn: (Row6) -> ARow): List = rows.map(fn)
+fun Table7.mapRows(fn: (Row7) -> ARow): List = rows.map(fn)
+fun Table8.mapRows(fn: (Row8) -> ARow): List = rows.map(fn)
+fun Table9.mapRows(fn: (Row9) -> ARow): List = rows.map(fn)
fun table(headers: Headers1, vararg rows: Row1) = Table1(headers, rows.asList())
fun table(headers: Headers2, vararg rows: Row2) = Table2(headers, rows.asList())
@@ -60,14 +70,52 @@ data class Table20(val headers: Headers21, val rows: List>)
data class Table22(val headers: Headers22, val rows: List>)
-// TODO I'm only supporting table with 3 arguments until the API stabilizes
-fun table(
- headers: Headers3,
- fileContent: String,
- transform: (String, String, String) -> Row3
-): Table3 {
- val table = StringTable(headers.values(), fileContent.lines())
- val rows = table.mapRows { (a, b, c) -> transform(a, b, c) }
- return Table3(headers, rows)
-}
+fun table(headers: Headers1, fileContent: String, transform: (String) -> Row1) = Table1(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0]) }
+)
+fun table(headers: Headers2, fileContent: String, transform: (String, String) -> Row2) = Table2(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1]) }
+)
+
+fun table(headers: Headers3, fileContent: String, transform:(String, String, String) -> Row3
+): Table3 = Table3(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2]) }
+)
+
+fun table(headers: Headers4, fileContent: String, transform:(String, String, String, String) -> Row4) = Table4(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3])}
+)
+fun table(headers: Headers5, fileContent: String, transform:(String, String, String, String, String) -> Row5) = Table5(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3], it[4])}
+)
+fun table(headers: Headers6, fileContent: String, transform:(String, String, String, String, String, String) -> Row6) = Table6(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3], it[4],it[5]) }
+)
+fun table(headers: Headers7, fileContent: String, transform:(String, String, String, String, String, String, String) -> Row7) = Table7(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3], it[4],it[5], it[6])}
+)
+fun table(headers: Headers8, fileContent: String, transform:(String, String, String, String, String, String, String, String) -> Row8) = Table8(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3], it[4],it[5], it[6], it[7])}
+)
+fun table(headers: Headers9, fileContent: String, transform:(String, String, String, String, String, String, String, String, String) -> Row9) = Table9(
+ headers = headers,
+ rows = StringTable(headers.values(), fileContent.lines())
+ .mapRows { transform(it[0], it[1], it[2], it[3], it[4],it[5], it[6], it[7], it[8])}
+)