Skip to content

Commit fec6ab3

Browse files
Steve Ramageclaude
andcommitted
feat: add 6 validators - cpuset_partition, sr_iov_{boolean,uint32}, managed_oom_mem_pressure_{limit,duration_sec}, dhcp_server_emit (Resolves #444)
First of ~5 batches to extend the OptionValueTest burn-down buffer through July. Each grammar mirrors the systemd C parser semantics: - config_parse_cpuset_partition: enum member|root|isolated (DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT) - config_parse_sr_iov_boolean: plain parse_boolean - config_parse_sr_iov_uint32: conservative uint32 (per-lvalue bounds skipped — no lvalue access) - config_parse_managed_oom_mem_pressure_limit: parse_permyriad, ASCII % form (0..100(.NN)?%) - config_parse_managed_oom_mem_pressure_duration_sec: parse_sec time value (>= 1s constraint is semantic and not expressible in grammar) - config_parse_dhcp_server_emit: whitespace-separated list of IPv4 addresses or _server_address; used by DHCPServer.DNS=/NTP=/SIP=/POP3=/SMTP=/LPR= (not the EmitDNS= booleans) OptionValueTest missing-function count drops 400 -> 394; found-key count rises 1744 -> 1780. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5065f32 commit fec6ab3

13 files changed

Lines changed: 422 additions & 0 deletions

src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fun getAllAIGeneratedValidators(): Map<Validator, OptionValueInformation> {
5454
Validator("config_parse_codel_bool", "QDISC_KIND_CODEL") to ConfigParseCodelBoolOptionValue() as OptionValueInformation,
5555
Validator("config_parse_codel_u32", "QDISC_KIND_CODEL") to ConfigParseCodelU32OptionValue() as OptionValueInformation,
5656
Validator("config_parse_collect_mode", "0") to ConfigParseCollectModeOptionValue() as OptionValueInformation,
57+
Validator("config_parse_cpuset_partition", "0") to ConfigParseCpusetPartitionOptionValue() as OptionValueInformation,
5758
Validator("config_parse_df", "0") to ConfigParseDfOptionValue() as OptionValueInformation,
5859
Validator("config_parse_dhcp6_client_start_mode", "0") to ConfigParseDhcp6ClientStartModeOptionValue() as OptionValueInformation,
5960
Validator("config_parse_dhcp6_pd_prefix_hint", "0") to ConfigParseDhcp6PdPrefixHintOptionValue() as OptionValueInformation,
@@ -64,6 +65,7 @@ fun getAllAIGeneratedValidators(): Map<Validator, OptionValueInformation> {
6465
Validator("config_parse_dhcp_route_metric", "AF_INET") to ConfigParseDhcpRouteMetricOptionValue() as OptionValueInformation,
6566
Validator("config_parse_dhcp_send_hostname", "AF_INET6") to ConfigParseDhcpSendHostnameOptionValue() as OptionValueInformation,
6667
Validator("config_parse_dhcp_send_hostname", "AF_INET") to ConfigParseDhcpSendHostnameOptionValue() as OptionValueInformation,
68+
Validator("config_parse_dhcp_server_emit", "0") to ConfigParseDhcpServerEmitOptionValue() as OptionValueInformation,
6769
Validator("config_parse_dhcp_server_persist_leases", "0") to ConfigParseDhcpServerPersistLeasesOptionValue() as OptionValueInformation,
6870
Validator("config_parse_dhcp_static_lease_address", "0") to ConfigParseDhcpStaticLeaseAddressOptionValue() as OptionValueInformation,
6971
Validator("config_parse_disable_controllers", "0") to ConfigParseDisableControllersOptionValue() as OptionValueInformation,
@@ -122,6 +124,8 @@ fun getAllAIGeneratedValidators(): Map<Validator, OptionValueInformation> {
122124
Validator("config_parse_macvlan_broadcast_queue_size", "0") to ConfigParseMacvlanBroadcastQueueSizeOptionValue() as OptionValueInformation,
123125
Validator("config_parse_macvlan_broadcast_queue_threshold", "0") to ConfigParseMacvlanBroadcastQueueThresholdOptionValue() as OptionValueInformation,
124126
Validator("config_parse_macvlan_mode", "0") to ConfigParseMacvlanModeOptionValue() as OptionValueInformation,
127+
Validator("config_parse_managed_oom_mem_pressure_duration_sec", "0") to ConfigParseManagedOomMemPressureDurationSecOptionValue() as OptionValueInformation,
128+
Validator("config_parse_managed_oom_mem_pressure_limit", "0") to ConfigParseManagedOomMemPressureLimitOptionValue() as OptionValueInformation,
125129
Validator("config_parse_mdi", "0") to ConfigParseMdiOptionValue() as OptionValueInformation,
126130
Validator("config_parse_pressure_watch", "0") to ConfigParseMemoryPressureWatchOptionValue() as OptionValueInformation,
127131
Validator("config_parse_mtu", "AF_INET6") to ConfigParseMtuOptionValue() as OptionValueInformation,
@@ -165,8 +169,10 @@ fun getAllAIGeneratedValidators(): Map<Validator, OptionValueInformation> {
165169
Validator("config_parse_rx_tx_queues", "0") to ConfigParseRxTxQueuesOptionValue() as OptionValueInformation,
166170
Validator("config_parse_service_restart_mode", "0") to ConfigParseServiceRestartModeOptionValue() as OptionValueInformation,
167171
Validator("config_parse_socket_defer_trigger", "0") to ConfigParseSocketDeferTriggerOptionValue() as OptionValueInformation,
172+
Validator("config_parse_sr_iov_boolean", "0") to ConfigParseSrIovBooleanOptionValue() as OptionValueInformation,
168173
Validator("config_parse_sr_iov_link_state", "0") to ConfigParseSrIovLinkStateOptionValue() as OptionValueInformation,
169174
Validator("config_parse_sr_iov_num_vfs", "0") to ConfigParseSrIovNumVfsOptionValue() as OptionValueInformation,
175+
Validator("config_parse_sr_iov_uint32", "0") to ConfigParseSrIovUint32OptionValue() as OptionValueInformation,
170176
Validator("config_parse_sr_iov_vlan_proto", "0") to ConfigParseSrIovVlanProtoOptionValue() as OptionValueInformation,
171177
Validator("config_parse_swap_priority", "0") to ConfigParseSwapPriorityOptionValue() as OptionValueInformation,
172178
Validator("config_parse_tasks_max", "0") to ConfigParseTasksMaxOptionValue() as OptionValueInformation,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for CPUSetPartition=.
8+
*
9+
* C function: config_parse_cpuset_partition, expanded via
10+
* DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT in src/core/cgroup-util.c. The accepted values are
11+
* exactly the entries of cpuset_partition_table in src/core/cgroup.c:
12+
* - member
13+
* - root
14+
* - isolated
15+
*
16+
* Empty also resolves (to the default _CPUSET_PARTITION_INVALID), but the inspector won't be
17+
* called on an empty value in any case.
18+
*/
19+
class ConfigParseCpusetPartitionOptionValue : SimpleGrammarOptionValues(
20+
"config_parse_cpuset_partition",
21+
SequenceCombinator(
22+
LiteralChoiceTerminal("member", "root", "isolated"),
23+
EOF()
24+
)
25+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for the [DHCPServer] address-list options: DNS=, NTP=, SIP=, POP3=, SMTP=, LPR=.
8+
*
9+
* (Not to be confused with the EmitDNS=, EmitNTP=, ... toggles, which are plain booleans.)
10+
*
11+
* C function: config_parse_dhcp_server_emit in src/network/networkd-dhcp-server.c. Each
12+
* whitespace-separated word is either the literal token `_server_address` (resolved to the
13+
* server's own address at runtime) or a non-null IPv4 address parsed via in_addr_from_string
14+
* (AF_INET, ...). The grammar reflects that.
15+
*/
16+
class ConfigParseDhcpServerEmitOptionValue : SimpleGrammarOptionValues(
17+
"config_parse_dhcp_server_emit",
18+
SequenceCombinator(
19+
AlternativeCombinator(LiteralChoiceTerminal("_server_address"), IPV4_ADDR),
20+
ZeroOrMore(SequenceCombinator(
21+
WhitespaceTerminal(),
22+
AlternativeCombinator(LiteralChoiceTerminal("_server_address"), IPV4_ADDR)
23+
)),
24+
EOF()
25+
)
26+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for ManagedOOMMemoryPressureDurationSec=.
8+
*
9+
* C function: config_parse_managed_oom_mem_pressure_duration_sec in src/core/load-fragment.c.
10+
* Empty resets to USEC_INFINITY. Otherwise calls parse_sec(rvalue) and additionally requires
11+
* the result to be at least 1s and strictly less than infinity.
12+
*
13+
* The grammar matches parse_sec syntax (which is the existing TIME_VALUE: "infinity" or a
14+
* compound time expression). The semantic "must be >= 1s and != infinity" can't be enforced
15+
* by a grammar without arithmetic — leaving that as a runtime check (the systemd parser will
16+
* still log a warning at unit load).
17+
*/
18+
class ConfigParseManagedOomMemPressureDurationSecOptionValue : SimpleGrammarOptionValues(
19+
"config_parse_managed_oom_mem_pressure_duration_sec",
20+
SequenceCombinator(
21+
OptionalWhitespacePrefix(TIME_VALUE),
22+
EOF()
23+
)
24+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for ManagedOOMMemoryPressureLimit=.
8+
*
9+
* C function: config_parse_managed_oom_mem_pressure_limit in src/core/load-fragment.c. After
10+
* a "supported for this unit type" check it calls parse_permyriad(rvalue), which accepts
11+
* N(.NN)?% / N(.N)?‰ / N‱. Only the ASCII percent form is matched here (the Unicode forms are
12+
* vanishingly rare in unit files).
13+
*/
14+
class ConfigParseManagedOomMemPressureLimitOptionValue : SimpleGrammarOptionValues(
15+
"config_parse_managed_oom_mem_pressure_limit",
16+
SequenceCombinator(
17+
AlternativeCombinator(
18+
// 0..99 with optional one or two decimal places, followed by %
19+
SequenceCombinator(IntegerTerminal(0, 100), ZeroOrOne(RegexTerminal("\\.[0-9]{1,2}", "\\.[0-9]{1,2}")), LiteralChoiceTerminal("%")),
20+
// Exactly 100% or 100.0% / 100.00%
21+
SequenceCombinator(LiteralChoiceTerminal("100"), ZeroOrOne(RegexTerminal("\\.0{1,2}", "\\.0{1,2}")), LiteralChoiceTerminal("%"))
22+
),
23+
EOF()
24+
)
25+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for the [SR-IOV] boolean options: MACSpoofCheck=, QueryReceiveSideScaling=, Trust=.
8+
*
9+
* C function: config_parse_sr_iov_boolean in src/shared/netif-sriov.c. After branching on the
10+
* lvalue it calls parse_boolean(rvalue), so the accepted syntax is exactly the values handled
11+
* by the existing BOOLEAN grammar terminal.
12+
*/
13+
class ConfigParseSrIovBooleanOptionValue : SimpleGrammarOptionValues(
14+
"config_parse_sr_iov_boolean",
15+
SequenceCombinator(
16+
BOOLEAN,
17+
EOF()
18+
)
19+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues
4+
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*
5+
6+
/**
7+
* Validator for the [SR-IOV] uint32 options: VirtualFunction=, VLANId=, QualityOfService=.
8+
*
9+
* C function: config_parse_sr_iov_uint32 in src/shared/netif-sriov.c. After branching on the
10+
* lvalue it calls safe_atou32. Tighter per-lvalue bounds exist (VLANId 1..4095,
11+
* VirtualFunction < INT_MAX) but they can't be expressed in this validator since it has no
12+
* access to the lvalue at evaluation time. The conservative shape is "any uint32".
13+
*/
14+
class ConfigParseSrIovUint32OptionValue : SimpleGrammarOptionValues(
15+
"config_parse_sr_iov_uint32",
16+
SequenceCombinator(
17+
IntegerTerminal(0, 4_294_967_296L),
18+
EOF()
19+
)
20+
)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest
4+
import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection
5+
import org.junit.Test
6+
7+
class ConfigParseCpusetPartitionOptionValueTest : AbstractUnitFileTest() {
8+
9+
@Test
10+
fun testValidValues() {
11+
// language="unit file (systemd)"
12+
val file = """
13+
[Service]
14+
CPUSetPartition=member
15+
CPUSetPartition=root
16+
CPUSetPartition=isolated
17+
""".trimIndent()
18+
19+
setupFileInEditor("file.service", file)
20+
enableInspection(InvalidValueInspection::class.java)
21+
val highlights = myFixture.doHighlighting()
22+
23+
assertSize(0, highlights)
24+
}
25+
26+
@Test
27+
fun testInvalidValues() {
28+
// language="unit file (systemd)"
29+
val file = """
30+
[Service]
31+
CPUSetPartition=<error descr="Invalid value">none</error>
32+
CPUSetPartition=<error descr="Invalid value">Member</error>
33+
CPUSetPartition=<error descr="Invalid value">root isolated</error>
34+
CPUSetPartition=<error descr="Invalid value">123</error>
35+
""".trimIndent()
36+
37+
setupFileInEditor("file.service", file)
38+
enableInspection(InvalidValueInspection::class.java)
39+
val highlights = myFixture.doHighlighting()
40+
41+
assertSize(4, highlights)
42+
}
43+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest
4+
import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection
5+
import org.junit.Test
6+
7+
class ConfigParseDhcpServerEmitOptionValueTest : AbstractUnitFileTest() {
8+
9+
@Test
10+
fun testValidValues() {
11+
// language="unit file (systemd)"
12+
val file = """
13+
[DHCPServer]
14+
DNS=192.168.1.1
15+
NTP=192.168.1.1 192.168.1.2
16+
SIP=_server_address
17+
POP3=_server_address 8.8.8.8
18+
SMTP=8.8.4.4 1.1.1.1 _server_address
19+
LPR=10.0.0.1
20+
""".trimIndent()
21+
22+
setupFileInEditor("file.network", file)
23+
enableInspection(InvalidValueInspection::class.java)
24+
val highlights = myFixture.doHighlighting()
25+
26+
assertSize(0, highlights)
27+
}
28+
29+
@Test
30+
fun testInvalidValues() {
31+
// language="unit file (systemd)"
32+
val file = """
33+
[DHCPServer]
34+
DNS=<error descr="Invalid value">not-an-ip</error>
35+
DNS=<error descr="Invalid value">192.168.1.256</error>
36+
NTP=<error descr="Invalid value">::1</error>
37+
SIP=<error descr="Invalid value">192.168.1.1, 192.168.1.2</error>
38+
LPR=<error descr="Invalid value">_other_address</error>
39+
""".trimIndent()
40+
41+
setupFileInEditor("file.network", file)
42+
enableInspection(InvalidValueInspection::class.java)
43+
val highlights = myFixture.doHighlighting()
44+
45+
assertSize(5, highlights)
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai
2+
3+
import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest
4+
import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection
5+
import org.junit.Test
6+
7+
class ConfigParseManagedOomMemPressureDurationSecOptionValueTest : AbstractUnitFileTest() {
8+
9+
@Test
10+
fun testValidValues() {
11+
// language="unit file (systemd)"
12+
val file = """
13+
[Service]
14+
ManagedOOMMemoryPressureDurationSec=infinity
15+
ManagedOOMMemoryPressureDurationSec=10
16+
ManagedOOMMemoryPressureDurationSec=30s
17+
ManagedOOMMemoryPressureDurationSec=2min
18+
ManagedOOMMemoryPressureDurationSec=1h
19+
ManagedOOMMemoryPressureDurationSec=1min 30s
20+
""".trimIndent()
21+
22+
setupFileInEditor("file.service", file)
23+
enableInspection(InvalidValueInspection::class.java)
24+
val highlights = myFixture.doHighlighting()
25+
26+
assertSize(0, highlights)
27+
}
28+
29+
@Test
30+
fun testInvalidValues() {
31+
// language="unit file (systemd)"
32+
val file = """
33+
[Service]
34+
ManagedOOMMemoryPressureDurationSec=<error descr="Invalid value">abc</error>
35+
ManagedOOMMemoryPressureDurationSec=<error descr="Invalid value">-1s</error>
36+
ManagedOOMMemoryPressureDurationSec=<error descr="Invalid value">10zz</error>
37+
""".trimIndent()
38+
39+
setupFileInEditor("file.service", file)
40+
enableInspection(InvalidValueInspection::class.java)
41+
val highlights = myFixture.doHighlighting()
42+
43+
assertSize(3, highlights)
44+
}
45+
}

0 commit comments

Comments
 (0)