Skip to content

Commit c4e7c57

Browse files
test(inverter): model the mixed fleet as a per-inverter list, sweep every create_missing_arg list (#5359)
The rate-path tests set the percent arg to a bare value, which is not how a mixed fleet reaches args; they now use a list with a configured sibling slot. The create_missing_arg test now checks every arg constructing a GS inverter builds, since the None padding applies to all of them, not just the rates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 161936f commit c4e7c57

2 files changed

Lines changed: 24 additions & 13 deletions

File tree

‎apps/predbat/inverter.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def create_missing_arg(self, arg, default):
319319
# below: the caller overwrites [self.id] with its dummy entity straight afterwards, so a
320320
# bare default left in another inverter's slot reads back as "configured" there too. On a
321321
# mixed fleet that turns a plain number into a phantom per-entry entity for a sibling
322-
# inverter that never asked for one, which the per-entry rate-rate path then treats as
322+
# inverter that never asked for one, which the per-entry rate path then treats as
323323
# set (#5359)
324324
self.base.args[arg] = [default if i == self.id else None for i in range(4)]
325325
elif len(self.base.args[arg]) <= self.id:

‎apps/predbat/tests/test_inverter.py‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,9 +1100,12 @@ def test_mixed_fleet_rate_path_from_own_entry(test_name, ha, inv, prev_rate, per
11001100

11011101
saved_percent_arg = inv.base.args.get(percent_arg)
11021102
try:
1103-
# Simulate a mixed fleet: the percent key exists in args (another inverter set it up) but
1104-
# this inverter's own index is unset.
1105-
inv.base.args[percent_arg] = percent_entity if percent_entry_set else None
1103+
# A mixed fleet as apps.yaml writes it: a per-inverter list in which a sibling inverter's
1104+
# slot names a percent entity, and this inverter's own slot is set or not per the case
1105+
fleet = [None] * (inv.id + 2)
1106+
fleet[inv.id] = percent_entity if percent_entry_set else None
1107+
fleet[inv.id + 1] = "number.sibling_" + percent_arg
1108+
inv.base.args[percent_arg] = fleet
11061109
ha.dummy_items[watt_entity] = prev_rate
11071110
ha.dummy_items[percent_entity] = 100 # Would read back as rate_max if the percent path were wrongly taken
11081111

@@ -1157,7 +1160,9 @@ def test_mixed_fleet_rate_write_skips_unset_entry(test_name, ha, inv, discharge=
11571160
# the percent key exists in args only because a sibling inverter in the fleet set it up -
11581161
# this inverter's own slot in that list is None.
11591162
inv.base.args[watt_arg] = watt_entity
1160-
inv.base.args[percent_arg] = None
1163+
fleet = [None] * (inv.id + 2)
1164+
fleet[inv.id + 1] = "number.sibling_" + percent_arg
1165+
inv.base.args[percent_arg] = fleet
11611166
inv.rest_data = None
11621167
inv.rest_api = None
11631168
ha.dummy_items[watt_entity] = 0
@@ -1199,31 +1204,37 @@ def test_create_missing_arg_does_not_fill_sibling_entries(test_name, my_predbat)
11991204
!= "power") before inverter 1 is even constructed, inverter 1's slot ended up holding a bare
12001205
number rather than None - which the per-entry "is this inverter's entry set" check added for
12011206
#5359 then misreads as a configured entity, since a truthy value is indistinguishable from one.
1207+
1208+
The padding applies to every arg create_missing_arg() builds, not just the rates, so every arg
1209+
that constructing inverter 0 turned into a list is checked.
12021210
"""
12031211
failed = False
12041212
print(f"**** Running Test: {test_name} ****")
12051213

12061214
saved_args = copy.deepcopy(my_predbat.args)
12071215
try:
12081216
my_predbat.args["num_inverters"] = 2
1209-
# GS (Solis) drives charge rate via current rather than power, so constructing inverter 0
1210-
# takes the create_missing_arg("charge_rate", ...) branch under test
1217+
# GS (Solis) drives charge rate via current rather than power and has no target SoC, so
1218+
# constructing inverter 0 takes several create_missing_arg() branches, charge_rate among them
12111219
my_predbat.args["inverter_type"] = ["GS", "GS"]
12121220
my_predbat.args.pop("charge_rate", None)
12131221
my_predbat.args.pop("charge_rate_percent", None)
1222+
before = {key: value for key, value in my_predbat.args.items() if isinstance(value, list)}
12141223

12151224
inv0 = Inverter(my_predbat, 0, quiet=True)
12161225
if inv0.inv_output_charge_control == "power":
12171226
print("ERROR: {} test assumes a non-power charge control to exercise create_missing_arg('charge_rate', ...)".format(test_name))
12181227
return 1
12191228

1220-
charge_rate_list = my_predbat.args.get("charge_rate")
1221-
if not isinstance(charge_rate_list, list) or len(charge_rate_list) < 2:
1222-
print("ERROR: {} expected charge_rate to be a per-inverter list covering inverter 1, got {}".format(test_name, charge_rate_list))
1223-
failed = True
1224-
elif charge_rate_list[1] is not None:
1225-
print("ERROR: {} expected inverter 1's charge_rate slot to stay None until inverter 1 is constructed, got {}".format(test_name, charge_rate_list[1]))
1229+
created = sorted(key for key, value in my_predbat.args.items() if isinstance(value, list) and key not in before)
1230+
if "charge_rate" not in created:
1231+
print("ERROR: {} expected charge_rate to be created as a per-inverter list, created {}".format(test_name, created))
12261232
failed = True
1233+
for key in created:
1234+
siblings = my_predbat.args[key][1:]
1235+
if any(slot is not None for slot in siblings):
1236+
print("ERROR: {} expected {}'s sibling slots to stay None until those inverters are constructed, got {}".format(test_name, key, siblings))
1237+
failed = True
12271238
finally:
12281239
my_predbat.args.clear()
12291240
my_predbat.args.update(saved_args)

0 commit comments

Comments
 (0)