From 68c10c7c3e3f36d1e7f3f0c707e88fbe1099a5ab Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 20 Jun 2026 10:24:19 -0700 Subject: [PATCH] feat: add 6 validators - unit_string_printf, ip_filter_bpf_progs, user_group_compat, io_device_weight, io_device_latency, delegate_subgroup (Resolves #463) Each grammar is calibrated against the systemd C parser, biased toward false-negatives over false-positives (rarer C checks that can't be modeled safely are left unenforced and documented in each KDoc): - config_parse_unit_string_printf (35 keys): any string, but '%' must be '%%' or a valid unit specifier (unit_full_printf table + COMMON_SYSTEM/ CREDS/TMP specifiers). Real specifier validation, not accept-everything. - config_parse_ip_filter_bpf_progs (12): absolute path (PATH_CHECK_ABSOLUTE), leading specifier allowed. - config_parse_user_group_compat (12): valid_user_group_name(RELAX) - no ':' '/' control chars or edge whitespace; numeric IDs allowed. - config_parse_io_device_weight (6): (cg_weight_parse). - config_parse_io_device_latency (6): (parse_sec). - config_parse_delegate_subgroup (6): cgroup name, !cg_needs_escape. OptionValueTest burn-down: 372 -> 366 missing functions (797 -> 720 missing key-instances). Each validator ships valid + invalid tests; full suite passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 6 +++ .../ConfigParseDelegateSubgroupOptionValue.kt | 29 ++++++++++++ .../ConfigParseIoDeviceLatencyOptionValue.kt | 29 ++++++++++++ .../ConfigParseIoDeviceWeightOptionValue.kt | 26 +++++++++++ .../ConfigParseIpFilterBpfProgsOptionValue.kt | 24 ++++++++++ .../ConfigParseUnitStringPrintfOptionValue.kt | 30 +++++++++++++ .../ConfigParseUserGroupCompatOptionValue.kt | 26 +++++++++++ ...figParseDelegateSubgroupOptionValueTest.kt | 44 +++++++++++++++++++ ...nfigParseIoDeviceLatencyOptionValueTest.kt | 42 ++++++++++++++++++ ...onfigParseIoDeviceWeightOptionValueTest.kt | 43 ++++++++++++++++++ ...figParseIpFilterBpfProgsOptionValueTest.kt | 41 +++++++++++++++++ ...figParseUnitStringPrintfOptionValueTest.kt | 43 ++++++++++++++++++ ...nfigParseUserGroupCompatOptionValueTest.kt | 43 ++++++++++++++++++ 13 files changed, 426 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateSubgroupOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceLatencyOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceWeightOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpFilterBpfProgsOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitStringPrintfOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUserGroupCompatOptionValue.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateSubgroupOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceLatencyOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceWeightOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpFilterBpfProgsOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitStringPrintfOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUserGroupCompatOptionValueTest.kt diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt index c2cc5b9..0a61160 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt @@ -10,6 +10,12 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.* fun getAllAIGeneratedValidators(): Map { val allValidators = mapOf( Validator("config_parse_6rd_prefix", "0") to ConfigParse6rdPrefixOptionValue() as OptionValueInformation, + Validator("config_parse_unit_string_printf", "0") to ConfigParseUnitStringPrintfOptionValue() as OptionValueInformation, + Validator("config_parse_ip_filter_bpf_progs", "0") to ConfigParseIpFilterBpfProgsOptionValue() as OptionValueInformation, + Validator("config_parse_user_group_compat", "0") to ConfigParseUserGroupCompatOptionValue() as OptionValueInformation, + Validator("config_parse_io_device_weight", "0") to ConfigParseIoDeviceWeightOptionValue() as OptionValueInformation, + Validator("config_parse_io_device_latency", "0") to ConfigParseIoDeviceLatencyOptionValue() as OptionValueInformation, + Validator("config_parse_delegate_subgroup", "0") to ConfigParseDelegateSubgroupOptionValue() as OptionValueInformation, Validator("config_parse_ad_actor_sys_prio", "0") to ConfigParseAdActorSysPrioOptionValue() as OptionValueInformation, Validator("config_parse_ad_user_port_key", "0") to ConfigParseAdUserPortKeyOptionValue() as OptionValueInformation, Validator("config_parse_address_families", "0") to ConfigParseAddressFamiliesOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateSubgroupOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateSubgroupOptionValue.kt new file mode 100644 index 0000000..93c7bee --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateSubgroupOptionValue.kt @@ -0,0 +1,29 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for DelegateSubgroup=. + * + * C function: config_parse_delegate_subgroup in src/core/load-fragment.c. The value is used + * verbatim (no specifier expansion) and must satisfy !cg_needs_escape() (src/basic/cgroup-util.c): + * it must be a valid filename (no '/', not "."/"..", <= NAME_MAX), must not start with '_' or '.', + * must not be one of "notify_on_release"/"release_agent"/"tasks", and must not start with + * "cgroup.". + * + * Grammar models all of those except the rarer "." rule (e.g. "cpu.") which we leave + * unenforced to avoid coupling to the kernel controller list / false positives. First character + * must not be '/', '.', '_' or a control char; subsequent characters must not be '/' or control; + * length <= 255. + */ +class ConfigParseDelegateSubgroupOptionValue : SimpleGrammarOptionValues( + "config_parse_delegate_subgroup", + SequenceCombinator( + RegexTerminal( + ".+", + "(?!cgroup\\.)(?!(?:tasks|release_agent|notify_on_release)$)[^/._\\x00-\\x1F][^/\\x00-\\x1F]{0,254}" + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceLatencyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceLatencyOptionValue.kt new file mode 100644 index 0000000..f0a0ffc --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceLatencyOptionValue.kt @@ -0,0 +1,29 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for IODeviceLatencyTargetSec=. + * + * C function: config_parse_io_device_latency in src/core/load-fragment.c. The value is + * " ": the first whitespace-separated word is a device path (specifier-expanded, + * path_simplify_and_warn with flags 0 -- not required to be absolute), and the remainder is a + * time span parsed by parse_sec(). + * + * Grammar: a non-whitespace path token, whitespace, then a time span: one or more + * "" components (units per systemd time syntax), or "infinity". The number + * may be fractional. The path is left unconstrained to match the lenient C parsing. + */ +class ConfigParseIoDeviceLatencyOptionValue : SimpleGrammarOptionValues( + "config_parse_io_device_latency", + SequenceCombinator( + RegexTerminal("\\S+", "\\S+"), + WhitespaceTerminal(), + RegexTerminal( + ".+", + "infinity|(?:\\d+(?:\\.\\d+)?\\s*(?:nsec|ns|usec|us|msec|ms|seconds|second|sec|s|minutes|minute|min|hours|hour|hr|h|days|day|d|weeks|week|w|months|month|M|years|year|y|m)?\\s*)+" + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceWeightOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceWeightOptionValue.kt new file mode 100644 index 0000000..9da1939 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIoDeviceWeightOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for IODeviceWeight=. + * + * C function: config_parse_io_device_weight in src/core/load-fragment.c. The value is + * " ": the first whitespace-separated word is a device path (specifier-expanded, + * path_simplify_and_warn with flags 0 -- NOT required to be absolute), and the remainder is parsed + * by cg_weight_parse(), which accepts an integer in [CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX] = + * [1, 10000] (src/basic/cgroup-util.h). + * + * Grammar: a non-whitespace path token, whitespace, then an integer 1..10000. The path is left + * unconstrained (any token) to match the lenient C parsing and avoid false positives. + */ +class ConfigParseIoDeviceWeightOptionValue : SimpleGrammarOptionValues( + "config_parse_io_device_weight", + SequenceCombinator( + RegexTerminal("\\S+", "\\S+"), + WhitespaceTerminal(), + IntegerTerminal(1, 10001), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpFilterBpfProgsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpFilterBpfProgsOptionValue.kt new file mode 100644 index 0000000..35ee1f0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpFilterBpfProgsOptionValue.kt @@ -0,0 +1,24 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for IPIngressFilterPath= / IPEgressFilterPath=. + * + * C function: config_parse_ip_filter_bpf_progs in src/core/load-fragment.c. It runs + * unit_path_printf() (specifier expansion) and then path_simplify_and_warn(PATH_CHECK_ABSOLUTE), + * i.e. the resolved value must be an absolute path. + * + * Grammar: the value must start with '/' (an absolute path) or '%' (a specifier that may resolve + * to an absolute path, e.g. %d/...); the remainder may be any non-control characters. Specifiers + * are accepted loosely here (we do not enumerate the path-specifier set), since for a path the + * meaningful, low-false-positive check is "must be absolute". The empty value resets the list. + */ +class ConfigParseIpFilterBpfProgsOptionValue : SimpleGrammarOptionValues( + "config_parse_ip_filter_bpf_progs", + SequenceCombinator( + RegexTerminal(".+", "(?:/|%)[^\\x00-\\x1F]*"), + EOF() + ) +) \ No newline at end of file diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitStringPrintfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitStringPrintfOptionValue.kt new file mode 100644 index 0000000..44b945c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitStringPrintfOptionValue.kt @@ -0,0 +1,30 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for the ~35 keys parsed by config_parse_unit_string_printf (Unit.Description, + * Service.SyslogIdentifier, Service.PAMName, Socket.SmackLabel, Mount.Type, Mount.Options, ...). + * + * C function: config_parse_unit_string_printf in src/core/load-fragment.c. It runs + * unit_full_printf() (specifier expansion) and then config_parse_string() (store as-is). There is + * therefore NO constraint on the resulting value beyond it being an ordinary (UTF-8) string -- so + * the only thing we can meaningfully validate is the specifier syntax: every '%' must be either a + * literal "%%" or a known unit specifier. unit_full_printf() returns an error (and the assignment + * is dropped) on an unknown specifier, so flagging those is faithful to systemd. + * + * Valid specifiers are the unit table plus COMMON_SYSTEM/CREDS/TMP_SPECIFIERS from + * src/core/unit-printf.c and src/shared/specifier.h: + * i I j J n N p P f y Y c r R C d D E L S t h s a A b B H l q m M o v w W g G u U T V + * + * Any other character (including non-ASCII, e.g. "Description=café") is accepted. The empty value + * (which resets the field) is also accepted. + */ +class ConfigParseUnitStringPrintfOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_string_printf", + SequenceCombinator( + RegexTerminal(".*", "(?:[^%]|%%|%[iIjJnNpPfyYcrRCdDELSthsaAbBHlqmMovwWgGuUTV])*"), + EOF() + ) +) \ No newline at end of file diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUserGroupCompatOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUserGroupCompatOptionValue.kt new file mode 100644 index 0000000..0b74730 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUserGroupCompatOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/** + * Validator for User= / Group= (and similar) keys parsed by config_parse_user_group_compat. + * + * C function: config_parse_user_group_compat in src/core/load-fragment.c. It runs unit_full_printf() + * (specifier expansion) and then valid_user_group_name() with + * VALID_USER_ALLOW_NUMERIC | VALID_USER_RELAX | VALID_USER_WARN. + * + * In RELAX mode (see src/basic/user-util.c) the name is checked only superficially: it must be + * non-empty, valid UTF-8, contain no control characters, contain no ':' or '/', and have no + * leading/trailing whitespace (interior whitespace is allowed). Numeric IDs are allowed. We model + * exactly those rules and leave the rarer RELAX checks (purely-numeric-but-not-a-uid, "-N", "."/ + * "..") unenforced rather than risk false positives. '%' is allowed (specifier syntax). The empty + * value resets the field. + */ +class ConfigParseUserGroupCompatOptionValue : SimpleGrammarOptionValues( + "config_parse_user_group_compat", + SequenceCombinator( + RegexTerminal(".+", "[^\\s:/\\x00-\\x1F](?:[^:/\\x00-\\x1F]*[^\\s:/\\x00-\\x1F])?"), + EOF() + ) +) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateSubgroupOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateSubgroupOptionValueTest.kt new file mode 100644 index 0000000..9172c46 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateSubgroupOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDelegateSubgroupOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Service] + DelegateSubgroup=supervisor + DelegateSubgroup=my-subgroup + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // cg_needs_escape rejects names starting with '_'/'.' or containing '/', the "cgroup." + // prefix, and the reserved names tasks/release_agent/notify_on_release. + // language=unit file (systemd) + val file = """ + [Service] + DelegateSubgroup=_leading-underscore + DelegateSubgroup=has/slash + DelegateSubgroup=cgroup.controllers + DelegateSubgroup=tasks + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceLatencyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceLatencyOptionValueTest.kt new file mode 100644 index 0000000..bf26ebc --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceLatencyOptionValueTest.kt @@ -0,0 +1,42 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIoDeviceLatencyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Service] + IODeviceLatencyTargetSec=/dev/sda 10ms + IODeviceLatencyTargetSec=/dev/sdb 1s + IODeviceLatencyTargetSec=/dev/sdc 0 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // A non-time-span latency, and a path with no latency, are invalid. + // language=unit file (systemd) + val file = """ + [Service] + IODeviceLatencyTargetSec=/dev/sda notatime + IODeviceLatencyTargetSec=/dev/sdb + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceWeightOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceWeightOptionValueTest.kt new file mode 100644 index 0000000..ac55773 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIoDeviceWeightOptionValueTest.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIoDeviceWeightOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Service] + IODeviceWeight=/dev/sda 100 + IODeviceWeight=/dev/sdb 10000 + IODeviceWeight=/dev/sdc 1 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // Weight must be in [1, 10000]; a path with no weight is also invalid. + // language=unit file (systemd) + val file = """ + [Service] + IODeviceWeight=/dev/sda 0 + IODeviceWeight=/dev/sda 20000 + IODeviceWeight=/dev/sda + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpFilterBpfProgsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpFilterBpfProgsOptionValueTest.kt new file mode 100644 index 0000000..e128309 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpFilterBpfProgsOptionValueTest.kt @@ -0,0 +1,41 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIpFilterBpfProgsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Service] + IPIngressFilterPath=/sys/fs/bpf/my-prog + IPEgressFilterPath=%d/credential-prog + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // path_simplify_and_warn(PATH_CHECK_ABSOLUTE) rejects relative paths. + // language=unit file (systemd) + val file = """ + [Service] + IPIngressFilterPath=relative/path + IPEgressFilterPath=./also-relative + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitStringPrintfOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitStringPrintfOptionValueTest.kt new file mode 100644 index 0000000..569b4f1 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitStringPrintfOptionValueTest.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitStringPrintfOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Unit] + Description=My Web Service + Description=Service for %i on %H + Description=100%% complete + Description=café and 日本 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // Unknown specifier (%e) and a trailing bare '%' are rejected by unit_full_printf. + // language=unit file (systemd) + val file = """ + [Unit] + Description=Bad %e specifier + Description=Ends with a percent % + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUserGroupCompatOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUserGroupCompatOptionValueTest.kt new file mode 100644 index 0000000..1a0e908 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUserGroupCompatOptionValueTest.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUserGroupCompatOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language=unit file (systemd) + val file = """ + [Service] + User=root + Group=my-group + User=1000 + User=%i + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // valid_user_group_name(RELAX) rejects ':' (passwd field separator) and '/'. + // language=unit file (systemd) + val file = """ + [Service] + User=bad:name + Group=bad/name + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +}