From 46152616163e58ed4fab2a4ee1d9d155cc14cd42 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 27 Dec 2024 11:52:24 -0700 Subject: [PATCH] feat: Add support LimitCPU=,LimitData,.... (Resolves #312) --- .../semanticdata/SemanticDataRepository.kt | 2 + .../optionvalues/RlimitOptionValue.kt | 82 ++ .../grammar/AlternativeCombinator.kt | 4 +- .../grammar/GrammarOptionValue.kt | 6 +- .../grammar/OptionalWhitespacePrefix.kt | 34 + .../grammar/SequenceCombinator.kt | 2 +- .../grammar/WhitespaceTerminal.kt | 29 + .../inspections/InvalidValueForRLimitTest.kt | 780 ++++++++++++++++++ ...pectionForAllowedCpuSetOptionValueTest.kt} | 12 +- ... => InvalidValueInspectionForCPUShares.kt} | 8 +- ... => InvalidValueInspectionForCPUWeight.kt} | 8 +- ...ectionForConfigParseSecOptionValueTest.kt} | 8 +- ...InspectionForExecDirectoriesOptionsTest.kt | 8 +- ...nvalidValueInspectionForExecOptionsTest.kt | 18 +- ...ValueInspectionForExecOutputOptionsTest.kt | 6 +- ...idValueInspectionForIOReadBandwidthMax.kt} | 8 +- ...> InvalidValueInspectionForImagePolicy.kt} | 12 +- ...dValueInspectionForPathOptionValueTest.kt} | 4 +- ...alidValueInspectionForSignalOptionsTest.kt | 6 +- ...kt => InvalidValueInspectionForTTYSize.kt} | 6 +- ...validValueInspectionForUnsignedInteger.kt} | 6 +- .../inspections/InvalidValueInspectionTest.kt | 44 +- .../optionvalues/OptionValueTest.kt | 2 +- .../optionvalues/grammar/Grammar.kt | 72 ++ 24 files changed, 1081 insertions(+), 86 deletions(-) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/RlimitOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/OptionalWhitespacePrefix.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/WhitespaceTerminal.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/InvalidValueForRLimitTest.kt rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForAllowedCpuSetOptionValueTest.kt => InvalidValueInspectionForAllowedCpuSetOptionValueTest.kt} (88%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForCPUShares.kt => InvalidValueInspectionForCPUShares.kt} (84%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForCPUWeight.kt => InvalidValueInspectionForCPUWeight.kt} (86%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForConfigParseSecOptionValueTest.kt => InvalidValueInspectionForConfigParseSecOptionValueTest.kt} (85%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForIOReadBandwidthMax.kt => InvalidValueInspectionForIOReadBandwidthMax.kt} (78%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForImagePolicy.kt => InvalidValueInspectionForImagePolicy.kt} (80%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForPathOptionValueTest.kt => InvalidValueInspectionForPathOptionValueTest.kt} (87%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForTTYSize.kt => InvalidValueInspectionForTTYSize.kt} (85%) rename src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/{InvalidValidInspectionForUnsignedInteger.kt => InvalidValueInspectionForUnsignedInteger.kt} (85%) 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 7796b386..b6252529 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 @@ -120,6 +120,8 @@ class SemanticDataRepository private constructor() { validatorMap.putAll(ImagePolicyOptionValue.validators) validatorMap.putAll(CPUWeightOptionValue.validators) validatorMap.putAll(CPUSharesOptionValue.validators) + + validatorMap.putAll(RlimitOptionValue.validators) // Scopes are not supported since they aren't standard unit files. fileClassToSectionNameToKeyValuesFromDoc["unit"]?.remove(SCOPE_KEYWORD) fileClassToSectionToKeyAndValidatorMap["unit"]?.remove(SCOPE_KEYWORD) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/RlimitOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/RlimitOptionValue.kt new file mode 100644 index 00000000..269f885e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/RlimitOptionValue.kt @@ -0,0 +1,82 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.util.Optional + +class RlimitOptionValue(grammar : Combinator) : GrammarOptionValue("config_parse_rlimit", grammar) { + companion object { + + val GENERIC_SEQ = + AlternativeCombinator( + OptionalWhitespacePrefix(FlexibleLiteralChoiceTerminal("infinity")), + OptionalWhitespacePrefix(IntegerTerminal(0, Int.MAX_VALUE))) + + val BYTE_SEQ = AlternativeCombinator( + FlexibleLiteralChoiceTerminal("infinity"), + SequenceCombinator( + OptionalWhitespacePrefix(IntegerTerminal(0, Int.MAX_VALUE)), + OptionalWhitespacePrefix(LiteralChoiceTerminal("K", "M", "G", "T", "P", "E")) + ), + OptionalWhitespacePrefix(IntegerTerminal(0, Int.MAX_VALUE))) + + val TIME_SEQ = AlternativeCombinator( + FlexibleLiteralChoiceTerminal("infinity"), + OneOrMore( + SequenceCombinator( + OptionalWhitespacePrefix(IntegerTerminal(0, Int.MAX_VALUE)), + OptionalWhitespacePrefix(FlexibleLiteralChoiceTerminal("usec", "us", "μs", "msec", "ms", "seconds", "second", "sec", "s", "minutes", "minute", "min", "m", "hours", "hour", "hr", "h", "days", "day", "d", "weeks", "week", "w", "months", "month", "M", "years", "year", "y")) + ) + ), + OptionalWhitespacePrefix(IntegerTerminal(0, Int.MAX_VALUE)) + ) + + val NICE_SEQ = AlternativeCombinator( + SequenceCombinator(LiteralChoiceTerminal("+", "-"), IntegerTerminal(0, 21)), + OptionalWhitespacePrefix(SequenceCombinator(IntegerTerminal(0, 41))), + ) + + val COLON = LiteralChoiceTerminal(":") + + val BYTE_RLIMIT = SequenceCombinator(AlternativeCombinator(SequenceCombinator(BYTE_SEQ, COLON, BYTE_SEQ), BYTE_SEQ), EOF()) + val TIME_RLIMIT = SequenceCombinator(AlternativeCombinator(SequenceCombinator(TIME_SEQ, COLON, TIME_SEQ), TIME_SEQ), EOF()) + val GENERIC_RLIMIT = SequenceCombinator(AlternativeCombinator(SequenceCombinator(GENERIC_SEQ, COLON, GENERIC_SEQ), GENERIC_SEQ), EOF()) + val NICE_RLIMIT = SequenceCombinator(AlternativeCombinator(SequenceCombinator(NICE_SEQ, COLON, NICE_SEQ), NICE_SEQ), EOF()) + + + val validators = mapOf( + // Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_CPU") to RlimitOptionValue(TIME_RLIMIT), + // Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_FSIZE") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_DATA") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_STACK") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_CORE") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_RSS") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_NOFILE") to RlimitOptionValue(GENERIC_RLIMIT), + // Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_AS") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_NPROC") to RlimitOptionValue(GENERIC_RLIMIT), + // Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_MEMLOCK") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_LOCKS") to RlimitOptionValue(GENERIC_RLIMIT), + // Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_SIGPENDING") to RlimitOptionValue(GENERIC_RLIMIT), + // Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_MSGQUEUE") to RlimitOptionValue(BYTE_RLIMIT), + // Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_NICE") to RlimitOptionValue(NICE_RLIMIT), + // Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_RTPRIO") to RlimitOptionValue(GENERIC_RLIMIT), + // Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) + Validator("config_parse_rlimit", "RLIMIT_RTTIME") to RlimitOptionValue(TIME_RLIMIT), + ) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/AlternativeCombinator.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/AlternativeCombinator.kt index 444a3010..69d68a58 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/AlternativeCombinator.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/AlternativeCombinator.kt @@ -5,7 +5,7 @@ import kotlin.math.max /** * This is a sequence of tokens that must match any of them. */ -class AlternativeCombinator(vararg val tokens: Combinator) : Combinator { +open class AlternativeCombinator(vararg val tokens: Combinator) : Combinator { fun match(value: String, offset: Int, f: (Combinator, String, Int) -> MatchResult): MatchResult { @@ -20,8 +20,6 @@ class AlternativeCombinator(vararg val tokens: Combinator) : Combinator { return match } - - if (match.tokens.size > longestTerminalMatch.size) { longestTerminalMatch = match.terminals longestTokenMatch = match.tokens diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/GrammarOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/GrammarOptionValue.kt index a3874f30..68bb0aba 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/GrammarOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/GrammarOptionValue.kt @@ -50,8 +50,6 @@ open class GrammarOptionValue( } holder.registerProblem(property.valueNode.psi, "${property.key}'s value does not match the expected format. Possible reasons include unrecognized characters or premature end of input.", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, tr) - - return } @@ -96,9 +94,9 @@ open class GrammarOptionValue( } } - holder.registerProblem(property.valueNode.psi, "${property.key}'s value is correctly format but seems invalid.", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, tr, *quickFixes.toTypedArray()) + holder.registerProblem(property.valueNode.psi, "${property.key}'s value is correctly formatted but seems invalid.", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, tr, *quickFixes.toTypedArray()) } else { - holder.registerProblem(property.valueNode.psi, "${property.key}'s value is correctly format but seems invalid.", ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + holder.registerProblem(property.valueNode.psi, "${property.key}'s value is correctly formatted but seems invalid.", ProblemHighlightType.GENERIC_ERROR_OR_WARNING) } diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/OptionalWhitespacePrefix.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/OptionalWhitespacePrefix.kt new file mode 100644 index 00000000..53fab5ec --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/OptionalWhitespacePrefix.kt @@ -0,0 +1,34 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +class OptionalWhitespacePrefix(val combinator: Combinator): + AlternativeCombinator( + SequenceCombinator(WhitespaceTerminal(), combinator), + combinator + ) { +// +// override fun SyntacticMatch(value: String, offset: Int): MatchResult { +// var newOffset = offset +// for(o in offset.. 611) { + if (totalMissingValidators > 530) { assertEquals("Number of missing validators is too high at ${totalMissingValidators} vs. found ${totalFoundValidators}", sortedList, "") } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Grammar.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Grammar.kt index 4ae0361e..fed38a94 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Grammar.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Grammar.kt @@ -660,4 +660,76 @@ class GrammarTest : TestCase() { assertEquals(NoMatch, eof.SemanticMatch(invalidFromOffset, garbage.length)) } + fun testOptionalWhitespacePrefixMatches() { + /** + * Fixture Setup + */ + + // This combinator should match the options passed in. + + val on = LiteralChoiceTerminal("on") + + + val optionalWhitespacePrefix = OptionalWhitespacePrefix(on) + + val semValid = "on" + val semValid2 = "\t on" + val invalid = "off" + + val garbage = "XX" + + val semValidFromOffset = "${garbage}${semValid}" + val semValid2FromOffset = "${garbage}${semValid2}" + val invalidFromOffset = "${garbage}${invalid}" + + /** + * Execute SUT & Verification + */ + var match = optionalWhitespacePrefix.SyntacticMatch(semValid, 0) + assertEquals(semValid.length, match.matchResult) + assertEquals(listOf("on"), match.tokens) + assertEquals(listOf("LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SemanticMatch(semValid, 0) + assertEquals(semValid.length, match.matchResult) + assertEquals(listOf("on"), match.tokens) + assertEquals(listOf("LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SyntacticMatch(semValid2, 0) + assertEquals(semValid2.length, match.matchResult) + assertEquals(listOf("\t ", "on"), match.tokens) + assertEquals(listOf("WhitespaceTerminal", "LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SemanticMatch(semValid2, 0) + assertEquals(semValid2.length, match.matchResult) + assertEquals(listOf("\t ", "on"), match.tokens) + assertEquals(listOf("WhitespaceTerminal", "LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + assertEquals(NoMatch, optionalWhitespacePrefix.SyntacticMatch(invalid, 0)) + assertEquals(NoMatch, optionalWhitespacePrefix.SemanticMatch(invalid, 0)) + + match = optionalWhitespacePrefix.SemanticMatch(semValidFromOffset, garbage.length) + assertEquals(semValidFromOffset.length, match.matchResult) + assertEquals(listOf("on"), match.tokens) + assertEquals(listOf("LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SyntacticMatch(semValidFromOffset, garbage.length) + assertEquals(semValidFromOffset.length, match.matchResult) + assertEquals(listOf("on"), match.tokens) + assertEquals(listOf("LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SemanticMatch(semValid2FromOffset, garbage.length) + assertEquals(semValid2FromOffset.length, match.matchResult) + assertEquals(listOf("\t ", "on"), match.tokens) + assertEquals(listOf("WhitespaceTerminal", "LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + match = optionalWhitespacePrefix.SyntacticMatch(semValid2FromOffset, garbage.length) + assertEquals(semValid2FromOffset.length, match.matchResult) + assertEquals(listOf("\t ", "on"), match.tokens) + assertEquals(listOf("WhitespaceTerminal", "LiteralChoiceTerminal"), TerminalTypes(match.terminals)) + + assertEquals(NoMatch, optionalWhitespacePrefix.SemanticMatch(invalidFromOffset, garbage.length)) + assertEquals(NoMatch, optionalWhitespacePrefix.SyntacticMatch(invalidFromOffset, garbage.length)) + } + }