From 898048513951b471a492afa23e46112d14bcb236 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 5 Mar 2020 03:00:59 +0000 Subject: CTAN sync 202003050300 --- .../arara/filehandling/FileHandlingUtilsTest.kt | 62 +++++++++ .../arara/filehandling/FileSearchingUtilsTest.kt | 58 +++++++++ .../arara/localization/LanguageCoverageTest.kt | 102 +++++++++++++++ .../arara/localization/LanguageReport.kt | 143 +++++++++++++++++++++ .../islandoftex/arara/localization/LanguageTest.kt | 20 +++ .../org/islandoftex/arara/model/SessionTest.kt | 76 +++++++++++ .../org/islandoftex/arara/utils/CommonUtilsTest.kt | 33 +++++ .../org/islandoftex/arara/utils/ExecutionTest.kt | 139 ++++++++++++++++++++ .../org/islandoftex/arara/utils/ExtensionTest.kt | 23 ++++ 9 files changed, 656 insertions(+) create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileHandlingUtilsTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileSearchingUtilsTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageCoverageTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageReport.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/model/SessionTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/CommonUtilsTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExecutionTest.kt create mode 100644 support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExtensionTest.kt (limited to 'support/arara/source/src/test/kotlin/org/islandoftex') diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileHandlingUtilsTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileHandlingUtilsTest.kt new file mode 100644 index 0000000000..55b13a8fe5 --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileHandlingUtilsTest.kt @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.filehandling + +import io.kotlintest.shouldBe +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec +import java.io.File +import java.nio.file.Files +import org.islandoftex.arara.Arara +import org.islandoftex.arara.configuration.AraraSpec +import org.islandoftex.arara.model.AraraException + +class FileHandlingUtilsTest : ShouldSpec({ + should("fail generating CRC sums on inexistent files") { + shouldThrow { + FileHandlingUtils.calculateHash(File("QUACK")) + } + } + should("generate correct CRC sums") { + FileHandlingUtils.calculateHash(File("../LICENSE")) shouldBe "2396b4e2" + FileHandlingUtils.calculateHash(File("../CODE_OF_CONDUCT.md")) shouldBe "536c426f" + } + + should("find correct extension") { + FileHandlingUtils.getFileExtension(File("QUACK")) shouldBe "" + FileHandlingUtils.getFileExtension(File("a.tex")) shouldBe "tex" + FileHandlingUtils.getFileExtension(File(".tex")) shouldBe "tex" + } + should("find correct basename") { + FileHandlingUtils.getBasename(File("QUACK")) shouldBe "QUACK" + FileHandlingUtils.getBasename(File("a.tex")) shouldBe "a" + FileHandlingUtils.getBasename(File(".tex")) shouldBe "" + } + + should("get subdirecotry relationship right") { + FileHandlingUtils.isSubDirectory(File("../docs"), File("..")) shouldBe true + FileHandlingUtils.isSubDirectory(File(".."), File("../docs")) shouldBe false + shouldThrow { + FileHandlingUtils.isSubDirectory(File("../LICENSE"), File("..")) + } + shouldThrow { + FileHandlingUtils.isSubDirectory(File(".."), File("../LICENSE")) + } + } + + should("detect changes on file") { + val file = Files.createTempFile(null, null).toFile() + val referenceBackup = Arara.config[AraraSpec.Execution.reference] + Arara.config[AraraSpec.Execution.reference] = file.parentFile.resolve("reference") + FileHandlingUtils.hasChanged(file) shouldBe true + FileHandlingUtils.hasChanged(file) shouldBe false + file.writeText("QUACK") + FileHandlingUtils.hasChanged(file) shouldBe true + FileHandlingUtils.hasChanged(file) shouldBe false + file.writeText("QUACK2") + FileHandlingUtils.hasChanged(file) shouldBe true + file.delete() + FileHandlingUtils.hasChanged(file) shouldBe true + FileHandlingUtils.hasChanged(file) shouldBe false + Arara.config[AraraSpec.Execution.reference] = referenceBackup + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileSearchingUtilsTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileSearchingUtilsTest.kt new file mode 100644 index 0000000000..47f9916c43 --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/filehandling/FileSearchingUtilsTest.kt @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.filehandling + +import io.kotlintest.shouldBe +import io.kotlintest.specs.ShouldSpec +import java.nio.file.Files +import java.nio.file.Path +import kotlin.reflect.full.declaredMemberFunctions +import kotlin.reflect.jvm.isAccessible + +class FileSearchingUtilsTest : ShouldSpec({ + // TODO: test implicit extensions + + fun prepareFileSystem(): Path { + val tempDir = Files.createTempDirectory(System.nanoTime().toString()) + tempDir.resolve("quack/quack").toFile().mkdirs() + listOf("quack", "quack/quack", "quack/quack/quack").forEach { + tempDir.resolve("$it.tex").toFile().writeText(" ") + tempDir.resolve("$it.txt").toFile().writeText(" ") + } + return tempDir + } + + should("fail looking up inexistent file") { + val lookupFile = FileSearchingUtils::class.declaredMemberFunctions + .first { it.name == "lookupFile" } + lookupFile.isAccessible = true + lookupFile.call(FileSearchingUtils, "QUACK") shouldBe null + } + + should("fail on existing directory") { + val lookupFile = FileSearchingUtils::class.declaredMemberFunctions + .first { it.name == "lookupFile" } + lookupFile.isAccessible = true + lookupFile.call(FileSearchingUtils, "../buildSrc") shouldBe null + } + + should("find file by extension") { + val tempDir = prepareFileSystem() + FileSearchingUtils.listFilesByExtensions(tempDir.toFile(), + listOf("tex"), false).toSet() shouldBe + setOf(tempDir.resolve("quack.tex").toFile()) + FileSearchingUtils.listFilesByExtensions(tempDir.toFile(), + listOf("tex"), true).toSet() shouldBe + listOf("quack", "quack/quack", "quack/quack/quack") + .map { tempDir.resolve("$it.tex").toFile() }.toSet() + } + should("find file by pattern") { + val tempDir = prepareFileSystem() + FileSearchingUtils.listFilesByPatterns(tempDir.toFile(), + listOf("*q*.txt"), false).toSet() shouldBe + setOf(tempDir.resolve("quack.txt").toFile()) + FileSearchingUtils.listFilesByPatterns(tempDir.toFile(), + listOf("q*.txt"), true).toSet() shouldBe + listOf("quack", "quack/quack", "quack/quack/quack") + .map { tempDir.resolve("$it.txt").toFile() }.toSet() + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageCoverageTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageCoverageTest.kt new file mode 100644 index 0000000000..704d3e319d --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageCoverageTest.kt @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.localization + +import ch.qos.cal10n.verifier.MessageKeyVerifier +import io.kotlintest.inspectors.forAll +import io.kotlintest.matchers.collections.shouldNotBeEmpty +import io.kotlintest.shouldBe +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Locale +import java.util.stream.Collectors +import org.islandoftex.arara.model.AraraException + +class LanguageCoverageTest : ShouldSpec({ + "locale definitions" { + /** + * Helper method, checks the provided locale (all keys set?). + * + * @param locale The locale. + * @return The size of the error list. + */ + fun check(locale: Locale): Int { + val errors = MessageKeyVerifier(Messages::class.java).verify(locale) + errors.forEach(System.err::println) + return errors.size + } + + should("succeed instantiating known locale") { + Language("en").locale.language shouldBe "en" + } + should("throw on unknown locale instantiation") { + shouldThrow { + Language("quack") + } + } + + should("not error on known localizations") { + listOf("en", "de", "nl", "it").forAll { + check(Locale(it)) shouldBe 0 + } + check(Locale("en", "QN")) shouldBe 0 + check(Locale("pt", "BR")) shouldBe 0 + } + } + "language coverage" { + /* + * Tests the localized messages, checking if all messages are properly + * quoted (but not necessarily whether they are loadable). + */ + should("get all strings from every language") { + // get all files + val files = Files.list( + Paths.get("src/main/resources/org/islandoftex/arara/localization")) + .map { p: Path -> + val f = p.toFile() + if (f.name.endsWith("properties") && !f.isDirectory) f + else null + } + .collect(Collectors.toList()) + .toList() + .filterNotNull() + files.shouldNotBeEmpty() + + // for each report, print + // the corresponding entry + files.map { file: File -> + try { + LanguageReport.analyze(file) + } catch (exception: IOException) { + throw AssertionError( + "Fatal exception: an error was raised while " + + "trying to read one of the languages. Please " + + "make sure all languages in the provided " + + "directory have read permission.") + } + }.forEach { report -> + // debug output + println(report.reference.name + + "\t" + String.format(" %2.2f%%", report.coverage)) + + // if there are problematic lines, + // add the current language report + if (report.lines.isNotEmpty()) { + // legend: S = Simple message, single quotes should not be doubled + // P = Parametrized message, single quotes must be doubled + + // build the beginning of the line + println(report.reference.name) + // print error lines + println(report.lines) + } + + report.coverage shouldBe 100.0f + } + } + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageReport.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageReport.kt new file mode 100644 index 0000000000..addcd0262c --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageReport.kt @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.localization + +import java.io.File + +/** + * Implements the language report model. + * + * @author Island of TeX + * @version 5.0 + * @since 5.0 + */ +data class LanguageReport( + /** + * The file reference. + */ + val reference: File, + /** + * Total of checked lines. + */ + val total: Int, + /** + * List of problematic lines and their corresponding error types. + */ + val lines: Map +) { + /** + * Language coverage of the file. + */ + val coverage: Float + get() = if (lines.isEmpty()) { + 100f + } else { + (1f - lines.size.toFloat() / total) * 100f + } + + companion object { + /** + * Analyzes the list of lines. + * + * @param file The file to read. + * @return The language report. + */ + internal fun analyze(file: File): LanguageReport { + // holds the current line number + var number = 1 + // holds the number of checked lines + var checked = 0 + + // flag that holds the + // current analysis + var check: Int + + val reportLines = mutableMapOf() + + // check every line of the language file + file.forEachLine { line -> + // let's only analyze lines + // that are not comments + if (!line.trim().startsWith("#")) { + // increment the checked + // line counter + checked++ + + // line is a parametrized message + check = if (line.contains("{0}")) { + // check the corresponding pattern + checkParametrizedMessage(line) + } else { + // check the corresponding pattern + checkMessage(line) + } + + // we found an error, + // report it + if (check != 0) { + // add line and error type to the report + reportLines[number] = if (check == 1) 'P' else 'S' + } + } + + // let's move to the next line + number++ + } + + // return the language report + return LanguageReport( + reference = file, + total = checked, + lines = reportLines) + } + + /** + * Checks if the provided message follows the simple format. + * + * @param text Message. + * @return An integer value. + */ + private fun checkMessage(text: String): Int { + var i = 0 + var c: Char + for (element in text) { + c = element + i = if (c == '\'') { + if (i == 1) { + return 2 + } else { + 1 + } + } else { + 0 + } + } + return 0 + } + + /** + * Checks if the provided message follows the parametrized format. + * + * @param text Message. + * @return An integer value. + */ + private fun checkParametrizedMessage(text: String): Int { + var i = 0 + var c: Char + for (element in text) { + c = element + if (c == '\'') { + i += 1 + } else { + if (i != 0) { + if (i != 2) { + return 1 + } else { + i = 0 + } + } + } + } + return 0 + } + } +} diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageTest.kt new file mode 100644 index 0000000000..92d38ebc9b --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/localization/LanguageTest.kt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.localization + +import io.kotlintest.shouldBe +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec +import java.util.Locale +import org.islandoftex.arara.model.AraraException + +class LanguageTest : ShouldSpec({ + should("instantiate with known code") { + Language("en").locale shouldBe Locale.ENGLISH + } + + should("throw on unknown language") { + shouldThrow { + Language("quack") + } + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/model/SessionTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/model/SessionTest.kt new file mode 100644 index 0000000000..7580d8c81e --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/model/SessionTest.kt @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +import io.kotlintest.shouldBe +import io.kotlintest.shouldNotThrow +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec + +class SessionTest : ShouldSpec({ + should("include all environment variables") { + Session.clear() + Session.updateEnvironmentVariables() + Session.contains("environment:PATH") shouldBe true + } + + should("properly check existence") { + Session.clear() + Session.put("A", "B") + Session.contains("A") shouldBe true + Session.contains("C") shouldBe false + } + + should("support insertion") { + Session.clear() + Session.put("A", "B") + Session.contains("A") shouldBe true + } + + should("support get") { + Session.clear() + Session.put("A", "B") + Session["A"] shouldBe "B" + } + + should("support removal") { + Session.clear() + Session.put("A", "B") + Session.put("C", "D") + Session.contains("A") shouldBe true + Session.contains("C") shouldBe true + Session.remove("A") + Session.contains("A") shouldBe false + Session.contains("C") shouldBe true + } + + should("clear itself") { + Session.clear() + Session.put("A", "B") + Session.put("C", "D") + Session.contains("A") shouldBe true + Session.contains("C") shouldBe true + Session.clear() + Session.contains("A") shouldBe false + Session.contains("C") shouldBe false + } + + should("throw on unknown removal") { + Session.clear() + Session.put("A", "B") + shouldNotThrow { + Session.remove("A") + } + shouldThrow { + Session.remove("C") + } + } + + should("throw on unknown getter") { + Session.clear() + Session.put("A", "B") + Session["A"] shouldBe "B" + shouldThrow { + Session["C"] + } + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/CommonUtilsTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/CommonUtilsTest.kt new file mode 100644 index 0000000000..b77b901ef6 --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/CommonUtilsTest.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.utils + +import io.kotlintest.shouldBe +import io.kotlintest.specs.ShouldSpec +import org.islandoftex.arara.Arara +import org.islandoftex.arara.configuration.AraraSpec +import org.islandoftex.arara.localization.Language + +class CommonUtilsTest : ShouldSpec({ + should("format bytes correctly") { + Arara.config[AraraSpec.Execution.language] = Language("en") + mapOf(800 to "800 B", + 1000 to "1.0 kB", + 1024 to "1.0 kB", + 1000000 to "1.0 MB").forEach { (key, value) -> + CommonUtils.byteSizeToString(key.toLong()) shouldBe value + } + } + + should("find system utils") { + CommonUtils.isOnPath("echo") shouldBe true + } + should("not find utils with fantasy name") { + // hopefully no one will have such a command in the path… + CommonUtils.isOnPath("echoQuackForArara") shouldBe false + } + + should("flatten lists correctly") { + CommonUtils.flatten(listOf(1, 2, listOf(3, 4, listOf(5, 6)))) + .toSet() shouldBe (setOf(1, 2, 3, 4, 5, 6) as Set) + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExecutionTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExecutionTest.kt new file mode 100644 index 0000000000..a0cc52145a --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExecutionTest.kt @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.utils + +import io.kotlintest.DoNotParallelize +import io.kotlintest.matchers.string.shouldContain +import io.kotlintest.matchers.string.shouldNotContain +import io.kotlintest.shouldBe +import io.kotlintest.shouldNotBe +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.PrintStream +import java.nio.file.Paths +import kotlin.time.ExperimentalTime +import org.islandoftex.arara.Arara +import org.islandoftex.arara.configuration.AraraSpec +import org.islandoftex.arara.configuration.Configuration +import org.islandoftex.arara.filehandling.FileSearchingUtils +import org.islandoftex.arara.model.AraraException +import org.islandoftex.arara.model.Extractor +import org.islandoftex.arara.model.Interpreter +import org.islandoftex.arara.ruleset.DirectiveUtils + +@ExperimentalTime +@DoNotParallelize +class ExecutionTest : ShouldSpec({ + fun getPathForTest(name: String): String = "src/test/resources/executiontests/$name" + fun outputForTest(testName: String, fileName: String = "$testName.tex"): + String { + val sysout = System.out + val output = ByteArrayOutputStream() + try { + System.setOut(PrintStream(output)) + Arara.config[AraraSpec.Execution.workingDirectory] = + Paths.get(getPathForTest(testName)) + Configuration.load() + Arara.config[AraraSpec.Execution.verbose] = true + FileSearchingUtils.discoverFile(fileName) + val directives = DirectiveUtils.process(Extractor.extract( + File("${getPathForTest(testName)}/$fileName"))) + Interpreter(directives).execute() + return output.toByteArray().toString(Charsets.UTF_8) + } catch (ex: Exception) { + throw ex + } finally { + System.setOut(sysout) + output.close() + } + } + + should("be able to store variables sessions") { + outputForTest("sessions") shouldContain "MeowQuack" + } + + should("honor falsy existence test") { + val file = File(getPathForTest("conditionals") + "/conditionals.quack") + if (file.exists()) file.delete() + val output = outputForTest("conditionals") + output shouldContain "QuackOne" + output shouldContain "QuackFour" + output shouldNotContain "QuackTwo" + output shouldNotContain "QuackThree" + } + should("honor truthy existence (falsy contains) test") { + val file = File(getPathForTest("conditionals") + "/conditionals.quack") + file.writeText("Meow") + val output = outputForTest("conditionals") + file.delete() + output shouldContain "QuackOne" + output shouldContain "QuackFour" + output shouldContain "QuackTwo" + output shouldNotContain "QuackThree" + } + should("honor truthy contains test") { + val file = File(getPathForTest("conditionals") + "/conditionals.quack") + file.writeText("Duck") + val output = outputForTest("conditionals") + file.delete() + output shouldContain "QuackOne" + output shouldContain "QuackFour" + output shouldContain "QuackTwo" + output shouldContain "QuackThree" + } + + should("track changes") { + val file = File(getPathForTest("changes") + "/arara.yaml") + if (file.exists()) file.delete() + outputForTest("changes") shouldContain "QuackOne" + outputForTest("changes") shouldNotContain "QuackOne" + } + + should("gracefully halt on halt rule") { + val output = outputForTest("halt") + output shouldContain "QuackOne" + output shouldNotContain "QuackTwo" + CommonUtils.exitStatus shouldBe 0 + } + should("forcefully halt on halt error rule") { + val output = outputForTest("halt-error") + output shouldContain "QuackOne" + output shouldNotContain "QuackTwo" + CommonUtils.exitStatus shouldNotBe 0 + } + + should("fail on invalid config") { + val exception = shouldThrow { + outputForTest("invalid-config") + } + exception.message shouldContain "could not parse the configuration" + } + + should("read foreign extension") { + val output = outputForTest("foreign-extension", "foreign-extension.my") + output shouldContain "QuackOne" + } + should("fail on unknown extension") { + shouldThrow { + outputForTest("foreign-extension", "foreign-extension.xy") + } + } + should("accept empty pattern on known extension") { + val output = outputForTest("known-extension") + output shouldContain "QuackOne" + } + + should("execute option-less directives") { + val output = outputForTest("simple-directive") + output shouldContain "The echoer" + output shouldContain "SUCCESS" + } + should("execute directive with options") { + val output = outputForTest("directive-with-options") + output shouldContain "The echoer" + output shouldContain "batchmode" + output shouldContain "SUCCESS" + output shouldNotContain "FAILURE" + } +}) diff --git a/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExtensionTest.kt b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExtensionTest.kt new file mode 100644 index 0000000000..cd41545ee9 --- /dev/null +++ b/support/arara/source/src/test/kotlin/org/islandoftex/arara/utils/ExtensionTest.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.utils + +import io.kotlintest.shouldBe +import io.kotlintest.shouldThrow +import io.kotlintest.specs.ShouldSpec + +class ExtensionTest : ShouldSpec({ + should("abbreviate strings correctly") { + "Quack quack".abbreviate(6) shouldBe "Quack…" + "Quack Quack".abbreviate(80) shouldBe "Quack Quack" + shouldThrow { "Quack".abbreviate(1) } + } + + should("center strings correctly") { + "Quack".center(3, '-') shouldBe "Quack" + "Quack".center(9, '-') shouldBe "--Quack--" + } + + should("wrap strings correctly") { + "This text should be wrapped".wrap(10) shouldBe "This text\nshould be\nwrapped" + } +}) -- cgit v1.2.3