diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/SemanticDataRepository.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/SemanticDataRepository.kt index cd8d385f..b5854de7 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/SemanticDataRepository.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/SemanticDataRepository.kt @@ -74,6 +74,7 @@ class SemanticDataRepository private constructor() { validatorMap.putAll(DocumentationOptionValue.validators) validatorMap.putAll(ModeStringOptionValue.validators) validatorMap.putAll(ExecOptionValue.validators) + validatorMap.putAll(ExecOutputOptionValue.validators) validatorMap.putAll(UnitDependencyOptionValue.validators) validatorMap.putAll(NullOptionValue.validators) validatorMap.putAll(MemoryLimitOptionValue.validators) @@ -85,6 +86,7 @@ class SemanticDataRepository private constructor() { validatorMap.putAll(ConfigParseSecValidators.validators) validatorMap.putAll(AllowedCpuSetOptionValue.validators) validatorMap.putAll(TtySizeOptionValue.validators) + validatorMap.putAll(ExecDirectoriesOptionValue.validators) // Scopes are not supported since they aren't standard unit files. diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecDirectoriesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecDirectoriesOptionValue.kt new file mode 100644 index 00000000..c5ee6f8e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecDirectoriesOptionValue.kt @@ -0,0 +1,37 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues + +import com.intellij.openapi.project.Project +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator + +class ExecDirectoriesOptionValue : OptionValueInformation { + + override fun getAutoCompleteOptions(project: Project): Set { + return emptySet() + } + + override fun getErrorMessage(value: String): String? { + val values = value.split("\\s+") + + // loop over element in values + for (element in values) { + if (element.startsWith("/")) { + return "Invalid path: '${element}'. Takes a list of absolute paths." + } else if (element.contains("..")) { + return "Invalid path: '${element}'. Path cannot contain `..`." + } + continue + } + + return null + } + + + override val validatorName: String + get() = VALIDATOR_NAME + + companion object { + private val VALIDATOR_NAME = "config_parse_exec_directories" + + val validators = mapOf(Validator(VALIDATOR_NAME, "0") to ExecDirectoriesOptionValue()) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecOutputOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecOutputOptionValue.kt new file mode 100644 index 00000000..adc0a8f0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ExecOutputOptionValue.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues + +import com.intellij.openapi.project.Project +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator + +class ExecOutputOptionValue() : OptionValueInformation { + override fun getAutoCompleteOptions(project: Project): Set { + return emptySet() + } + + override fun getErrorMessage(value: String): String? { + + if (value.startsWith("file:") || value.startsWith("append:") || value.startsWith("truncate:") || value.startsWith("fd:")) { + val firstArg = value.split(":", limit = 2)[0] + val secondArg = value.split(":", limit = 2)[1] + + if (secondArg.trim().isEmpty()) { + when(firstArg) { + "file", "append", "truncate" -> return "path must be specified after the colon." + "fd" -> return "name must be specified after the colon." + } + } + + // We could do a better job of validating paths later + return null + } + + when (value) { + "inherit", "null", "tty", "journal", "kmsg", "journal+console", "kmsg+console", "socket" -> return null + else -> return "Takes one of inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, truncate:path, socket or fd:name." + + } + } + + override val validatorName: String + get() = VALIDATOR_NAME + + + companion object { + private val VALIDATOR_NAME = "config_parse_exec_output" + + val validators = mapOf(Validator(VALIDATOR_NAME, "0") to ExecOutputOptionValue()) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecDirectoriesOptionsTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecDirectoriesOptionsTest.kt new file mode 100644 index 00000000..ee3feca3 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecDirectoriesOptionsTest.kt @@ -0,0 +1,134 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections + +import junit.framework.TestCase +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest + +class InvalidValueInspectionForExecDirectoriesOptionsTest : AbstractUnitFileTest() { + + fun testNoWarningWhenUsingASingleRelativeDirectory() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=foo + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(0, highlights) + } + + fun testNoWarningWhenUsingSeveralRelativeDirectories() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=foo bar \ + hello + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(0, highlights) + } + + fun testWarningWhenUsingASingleAbsoluteDirectory() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=/tmp/myunit + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("Takes a list of absolute paths", info!!.description) + TestCase.assertEquals("/tmp/myunit", info.text) + } + + fun testWarningWhenUsingSeveralAbsoluteDirectory() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=/tmp/myunit /tmp/myunit2 + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("Takes a list of absolute paths", info!!.description) + TestCase.assertEquals("/tmp/myunit /tmp/myunit2", info.text) + } + + fun testWarningWhenUsingParentDirectoryOperator() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=../myunit + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("Path cannot contain `..`", info!!.description) + TestCase.assertEquals("../myunit", info.text) + } + + fun testWarningWhenUsingParentDirectoryOperatorInAThirdDirectory() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + RuntimeDirectory=foo/bar test/tmp/ ../myunit + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("Path cannot contain `..`", info!!.description) + TestCase.assertEquals("foo/bar test/tmp/ ../myunit", info.text) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecOutputOptionsTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecOutputOptionsTest.kt new file mode 100644 index 00000000..08ba617b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueInspectionForExecOutputOptionsTest.kt @@ -0,0 +1,110 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections + +import junit.framework.TestCase +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest + +class InvalidValueInspectionForExecOutputOptionsTest : AbstractUnitFileTest() { + + + fun testNoWarningWhenUsingValidValuesForOutput() { + runValidValueTest("inherit") + runValidValueTest("null") + runValidValueTest("tty") + runValidValueTest("journal") + runValidValueTest("kmsg") + runValidValueTest("journal+console") + runValidValueTest("kmsg+console") + runValidValueTest("file:/dev/null") + runValidValueTest("append:/tmp/log.txt") + runValidValueTest("truncate:/tmp/path") + runValidValueTest("socket") + runValidValueTest("fd:stdout") + } + + fun runValidValueTest(validValue : String) { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + StandardOutput=$validValue + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(0, highlights) + } + + + fun testWeakWarningWhenUsingInvalidValueForExecOption() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + StandardOutput=foo + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("Takes one of", info!!.description) + TestCase.assertEquals("foo", info.text) + } + + fun testWeakWarningWhenMissingSecondArgumentForFd() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + StandardOutput=fd: + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("name must be specified after the colon", info!!.description) + TestCase.assertEquals("fd:", info.text) + } + + fun testWeakWarningWhenMissingSecondArgumentForFile() { + // Fixture Setup + // language="unit file (systemd)" + val file = """ + [Service] + StandardOutput=file: + + """.trimIndent() + + + // Execute SUT + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + // Verification + assertSize(1, highlights) + val info = highlights[0] + AbstractUnitFileTest.Companion.assertStringContains("path must be specified after the colon", info!!.description) + TestCase.assertEquals("file:", info.text) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt index 3dbcb3b9..8de7959a 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt @@ -12,6 +12,7 @@ class OptionValueTest : AbstractUnitFileTest() { val missingValidators = hashMapOf() var totalMissingValidators = 0 + var totalFoundValidators = 0 for (sectionName in SemanticDataRepository.instance.sectionNamesFromValidators) { for (key in SemanticDataRepository.instance.getAllowedKeywordsInSectionFromValidators(sectionName)) { @@ -21,6 +22,8 @@ class OptionValueTest : AbstractUnitFileTest() { if (!validatorMap.containsKey(validator)) { missingValidators[validator] = (missingValidators[validator] ?: 0) + 1 totalMissingValidators++ + } else { + totalFoundValidators++ } } } @@ -29,8 +32,9 @@ class OptionValueTest : AbstractUnitFileTest() { val sortedList = missingValidatorList.sortedDescending().joinToString("\n") - println(totalMissingValidators) - if (totalMissingValidators > 630) { + println("Missing:$totalMissingValidators") + println("Found:$totalFoundValidators") + if (totalMissingValidators > 600) { assertEquals(sortedList, "") }