IQ.Pilot Prebuilt Release @ 27f668a

This commit is contained in:
IQ.Lvbs CI [bot]
2026-09-03 18:23:24 -05:00
commit b073c5182b
2554 changed files with 679696 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import argparse
import collections
import multiprocessing
import os
import requests
from tqdm import tqdm
import iqpilot.system.hardware.tici.casync as casync
def get_chunk_download_size(chunk):
sha = chunk.sha.hex()
path = os.path.join(remote_url, sha[:4], sha + ".cacnk")
if os.path.isfile(path):
return os.path.getsize(path)
else:
r = requests.head(path, timeout=10)
r.raise_for_status()
return int(r.headers['content-length'])
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Compute overlap between two casync manifests')
parser.add_argument('frm')
parser.add_argument('to')
args = parser.parse_args()
frm = casync.parse_caibx(args.frm)
to = casync.parse_caibx(args.to)
remote_url = args.to.replace('.caibx', '')
most_common = collections.Counter(t.sha for t in to).most_common(1)[0][0]
frm_dict = casync.build_chunk_dict(frm)
# Get content-length for each chunk
with multiprocessing.Pool() as pool:
szs = list(tqdm(pool.imap(get_chunk_download_size, to), total=len(to)))
chunk_sizes = {t.sha: sz for (t, sz) in zip(to, szs, strict=True)}
sources: dict[str, list[int]] = {
'seed': [],
'remote_uncompressed': [],
'remote_compressed': [],
}
for chunk in to:
# Assume most common chunk is the zero chunk
if chunk.sha == most_common:
continue
if chunk.sha in frm_dict:
sources['seed'].append(chunk.length)
else:
sources['remote_uncompressed'].append(chunk.length)
sources['remote_compressed'].append(chunk_sizes[chunk.sha])
print()
print("Update statistics (excluding zeros)")
print()
print("Download only with no seed:")
print(f" Remote (uncompressed)\t\t{sum(sources['seed'] + sources['remote_uncompressed']) / 1000 / 1000:.2f} MB\tn = {len(to)}")
print(f" Remote (compressed download)\t{sum(chunk_sizes.values()) / 1000 / 1000:.2f} MB\tn = {len(to)}")
print()
print("Upgrade with seed partition:")
print(f" Seed (uncompressed)\t\t{sum(sources['seed']) / 1000 / 1000:.2f} MB\t\t\t\tn = {len(sources['seed'])}")
sz, n = sum(sources['remote_uncompressed']), len(sources['remote_uncompressed'])
print(f" Remote (uncompressed)\t\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}")
sz, n = sum(sources['remote_compressed']), len(sources['remote_compressed'])
print(f" Remote (compressed download)\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}")

View File

@@ -0,0 +1,37 @@
import json
import os
import requests
TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
MANIFESTS = [
os.path.join(TEST_DIR, "../agnos.json"),
os.path.join(TEST_DIR, "../agnos_tici_15_1.json"),
]
IMAGE_HOST = "git.konn3kt.com"
XZ_MAGIC = b"\xfd7zXZ\x00"
LFS_POINTER_MAGIC = b"version https://git-lfs"
class TestAgnosUpdater:
def test_manifest(self):
for manifest in MANIFESTS:
with open(manifest) as f:
m = json.load(f)
for img in m:
assert img['url'].split('/')[2] == IMAGE_HOST
if not img['sparse']:
assert img['hash'] == img['hash_raw']
s = requests.Session()
s.trust_env = False
r = s.get(img['url'], timeout=10, stream=True,
headers={"User-Agent": "IQOS-Updater"})
if r.status_code in (401, 403, 404):
continue
head = next(r.iter_content(chunk_size=256), b"") or b""
assert not head.startswith(XZ_MAGIC), f"{img['name']}: anonymous request served image content"
assert not head.startswith(LFS_POINTER_MAGIC), f"{img['name']}: anonymous request served the LFS pointer"

View File

@@ -0,0 +1,66 @@
import pytest
import time
import random
import subprocess
from panda import Panda
from iqpilot.system.hardware import HARDWARE
from iqpilot.system.hardware.tici.hardware import Tici
from iqpilot.system.hardware.tici.amplifier import Amplifier
@pytest.mark.tici
class TestAmplifier:
def setup_method(self):
# clear dmesg
subprocess.check_call("sudo dmesg -C", shell=True)
HARDWARE.reset_internal_panda()
Panda.wait_for_panda(None, 30)
self.panda = Panda()
def teardown_method(self):
HARDWARE.reset_internal_panda()
def _check_for_i2c_errors(self, expected):
dmesg = subprocess.check_output("dmesg", shell=True, encoding='utf8')
i2c_lines = [l for l in dmesg.strip().splitlines() if 'i2c_geni a88000.i2c' in l]
i2c_str = '\n'.join(i2c_lines)
if not expected:
return len(i2c_lines) == 0
else:
return "i2c error :-107" in i2c_str or "Bus arbitration lost" in i2c_str
def test_init(self):
amp = Amplifier(debug=True)
r = amp.initialize_configuration(Tici().get_device_type())
assert r
assert self._check_for_i2c_errors(False)
def test_shutdown(self):
amp = Amplifier(debug=True)
for _ in range(10):
r = amp.set_global_shutdown(True)
r = amp.set_global_shutdown(False)
# amp config should be successful, with no i2c errors
assert r
assert self._check_for_i2c_errors(False)
def test_init_while_siren_play(self):
for _ in range(10):
self.panda.set_siren(False)
time.sleep(0.1)
self.panda.set_siren(True)
time.sleep(random.randint(0, 5))
amp = Amplifier(debug=True)
r = amp.initialize_configuration(Tici().get_device_type())
assert r
if self._check_for_i2c_errors(True):
break
else:
pytest.fail("didn't hit any i2c errors")

View File

@@ -0,0 +1,238 @@
import pytest
from iqpilot.system.hardware import HARDWARE
from iqpilot.system.hardware.base import LPAError, LPAProfileNotFoundError, Profile
from iqpilot.system.hardware.tici import lpa as lpa_module
from iqpilot.system.hardware.tici.esim_manager import EsimManager
# https://euicc-manual.osmocom.org/docs/rsp/known-test-profile
# iccid is always the same for the given activation code
TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5'
TEST_ICCID = '8944476500001944011'
TEST_NICKNAME = 'test_profile'
def cleanup():
lpa = HARDWARE.get_sim_lpa()
try:
lpa.delete_profile(TEST_ICCID)
except LPAProfileNotFoundError:
pass
lpa.process_notifications()
@pytest.mark.tici
class TestEsim:
@classmethod
def setup_class(cls):
cleanup()
@classmethod
def teardown_class(cls):
cleanup()
def test_provision_enable_disable(self):
lpa = HARDWARE.get_sim_lpa()
current_active = lpa.get_active_profile()
lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME)
assert any(p.iccid == TEST_ICCID and p.nickname == TEST_NICKNAME for p in lpa.list_profiles())
lpa.enable_profile(TEST_ICCID)
new_active = lpa.get_active_profile()
assert new_active is not None
assert new_active.iccid == TEST_ICCID
assert new_active.nickname == TEST_NICKNAME
lpa.disable_profile(TEST_ICCID)
new_active = lpa.get_active_profile()
assert new_active is None
if current_active:
lpa.enable_profile(current_active.iccid)
class TestEsimDeleteHandling:
def test_delete_ignores_notification_cleanup_if_profile_is_gone(self, monkeypatch):
target_iccid = "89012804332267989477"
lpa = lpa_module.TiciLPA()
monkeypatch.setattr(lpa, "_validate_profile_exists", lambda iccid: None)
monkeypatch.setattr(lpa, "get_active_profile", lambda: Profile("8901240527117095243", "US Mobile", True, "Wireless"))
monkeypatch.setattr(lpa, "_restart_modem", lambda: None)
monkeypatch.setattr(
lpa,
"list_profiles",
lambda: [Profile("8901240527117095243", "US Mobile", True, "Wireless")],
)
monkeypatch.setattr(lpa, "_ensure_client", lambda: object())
monkeypatch.setattr(lpa_module, "delete_profile", lambda client, iccid: None)
def fail_notifications(client):
raise RuntimeError('AT command failed (AT+CGLA=2,16,"80E2910003BF2800"): AT command failed')
monkeypatch.setattr(lpa_module, "process_notifications", fail_notifications)
lpa.delete_profile(target_iccid)
def test_delete_raises_clear_error_if_profile_still_present_after_cleanup_failure(self, monkeypatch):
target_iccid = "89012804332267989477"
lpa = lpa_module.TiciLPA()
monkeypatch.setattr(lpa, "_validate_profile_exists", lambda iccid: None)
monkeypatch.setattr(lpa, "get_active_profile", lambda: Profile("8901240527117095243", "US Mobile", True, "Wireless"))
monkeypatch.setattr(lpa, "_restart_modem", lambda: None)
monkeypatch.setattr(
lpa,
"list_profiles",
lambda: [
Profile("8901240527117095243", "US Mobile", True, "Wireless"),
Profile(target_iccid, "RedPocket", False, "RedPocket"),
],
)
monkeypatch.setattr(lpa, "_ensure_client", lambda: object())
monkeypatch.setattr(lpa_module, "delete_profile", lambda client, iccid: None)
def fail_notifications(client):
raise RuntimeError('AT command failed (AT+CGLA=2,16,"80E2910003BF2800"): AT command failed')
monkeypatch.setattr(lpa_module, "process_notifications", fail_notifications)
with pytest.raises(LPAError, match="Profile delete did not finish cleanly"):
lpa.delete_profile(target_iccid)
def test_manager_maps_notification_cleanup_error(self):
error = LPAError('AT command failed (AT+CGLA=2,16,"80E2910003BF2800"): AT command failed')
assert EsimManager._map_error(error) == "Modem notification cleanup failed; refresh profiles"
class TestEsimNotificationCleanupRecovery:
def test_switch_ignores_notification_cleanup_if_target_is_enabled(self, monkeypatch):
target_iccid = "8901240527117194095"
lpa = lpa_module.TiciLPA()
monkeypatch.setattr(lpa, "_validate_profile_exists", lambda iccid: None)
monkeypatch.setattr(lpa, "_ensure_switchable_profile", lambda iccid: None)
monkeypatch.setattr(lpa, "get_active_profile", lambda: Profile("8901240527117113293", "US Mobile", True, "Wireless"))
monkeypatch.setattr(lpa, "_wait_for_modem", lambda: None)
monkeypatch.setattr(lpa, "_ensure_client", lambda: type("Client", (), {"channel": "2", "_use_csim": False})())
monkeypatch.setattr(
lpa,
"list_profiles",
lambda: [
Profile("8901240527117113293", "US Mobile", False, "Wireless"),
Profile(target_iccid, "T-Mobile", True, "Wireless"),
],
)
monkeypatch.setattr(lpa_module, "enable_profile", lambda client, iccid, refresh=True: None)
monkeypatch.setattr(
lpa_module,
"process_notifications",
lambda client: (_ for _ in ()).throw(RuntimeError('AT command failed (AT+CGLA=2,16,"80E2910003BF2800"): AT command failed')),
)
lpa.switch_profile(target_iccid)
@pytest.mark.parametrize(("is_eg25", "expected_refresh", "expected_waits", "expected_reboots"), [
(True, True, 1, 0),
(False, False, 0, 1),
])
def test_switch_profile_uses_modem_specific_refresh_behavior(self, monkeypatch, is_eg25, expected_refresh, expected_waits, expected_reboots):
target_iccid = "8901240527117194095"
lpa = object.__new__(lpa_module.TiciLPA)
lpa._is_eg25 = is_eg25
lpa.verbose = False
waits = []
reboots = []
refresh_values = []
monkeypatch.setattr(lpa, "_validate_profile_exists", lambda iccid: None)
monkeypatch.setattr(lpa, "_ensure_switchable_profile", lambda iccid: None)
monkeypatch.setattr(lpa, "get_active_profile", lambda: Profile("8901240527117113293", "US Mobile", True, "Wireless"))
monkeypatch.setattr(lpa, "_wait_for_modem", lambda: waits.append(True))
monkeypatch.setattr(lpa, "_restart_modem", lambda: reboots.append(True))
monkeypatch.setattr(lpa, "_with_lpa_error", lambda fn: fn())
monkeypatch.setattr(lpa, "_ensure_client", lambda: type("Client", (), {"channel": "2", "_use_csim": False})())
monkeypatch.setattr(
lpa,
"_process_notifications_after_state_change",
lambda validator, _recovery_message, _failure_message: validator(),
)
monkeypatch.setattr(
lpa,
"list_profiles",
lambda: [
Profile("8901240527117113293", "US Mobile", False, "Wireless"),
Profile(target_iccid, "T-Mobile", True, "Wireless"),
],
)
def fake_enable_profile(client, iccid, refresh=True):
refresh_values.append(refresh)
monkeypatch.setattr(lpa_module, "enable_profile", fake_enable_profile)
lpa.switch_profile(target_iccid)
assert refresh_values == [expected_refresh]
assert len(waits) == expected_waits
assert len(reboots) == expected_reboots
def test_download_ignores_notification_cleanup_if_profile_exists(self, monkeypatch, mocker):
target_iccid = "8901240527117194095"
lpa = lpa_module.TiciLPA()
profiles = [
[Profile("8901240527117113293", "US Mobile", True, "Wireless")],
[
Profile("8901240527117113293", "US Mobile", True, "Wireless"),
Profile(target_iccid, "T-Mobile", False, "Wireless"),
],
[
Profile("8901240527117113293", "US Mobile", True, "Wireless"),
Profile(target_iccid, "T-Mobile", False, "Wireless"),
],
]
monkeypatch.setattr(lpa, "_ensure_client", lambda: object())
monkeypatch.setattr(lpa, "_wait_for_modem", lambda: None)
profile_states = iter(profiles)
current_profiles = profiles[-1]
def list_profiles():
nonlocal current_profiles
current_profiles = next(profile_states, current_profiles)
return current_profiles
monkeypatch.setattr(lpa, "list_profiles", list_profiles)
monkeypatch.setattr(lpa_module, "download_profile", lambda client, qr: target_iccid)
set_nickname = mocker.MagicMock()
monkeypatch.setattr(lpa_module, "set_profile_nickname", set_nickname)
monkeypatch.setattr(
lpa_module,
"process_notifications",
lambda client: (_ for _ in ()).throw(RuntimeError('AT command failed (AT+CGLA=2,16,"80E2910003BF2800"): AT command failed')),
)
lpa.download_profile(TEST_ACTIVATION_CODE, "T-Mobile")
set_nickname.assert_called_once_with(mocker.ANY, target_iccid, "T-Mobile")
class TestEsimManagerSupportGating:
def test_refresh_profiles_does_not_touch_lpa_without_euicc(self, monkeypatch, mocker):
manager = EsimManager()
monkeypatch.setattr(manager, "_query_euicc_support", lambda: False)
manager._params = mocker.MagicMock()
manager._params.get.return_value = None
manager._params.get_bool.return_value = True
monkeypatch.setattr(HARDWARE, "get_device_type", lambda: "tici")
monkeypatch.setattr(HARDWARE, "get_sim_lpa", lambda: (_ for _ in ()).throw(AssertionError("LPA should not be touched")))
manager.refresh_profiles()
assert manager.get_state().profiles == []
assert manager.get_state().message == "Insert the original comma SIM card that came with the device to use eSIM"

View File

@@ -0,0 +1,102 @@
from iqpilot.system.hardware.tici.hardware import (
MM_MODEM_ACCESS_TECHNOLOGY_LTE,
MM_MODEM_STATE,
NMActiveConnectionState,
Tici,
)
from iqpilot.cereal import log
def _make_connection(mocker, connection_type: str, state: int):
connection = mocker.MagicMock()
def get_side_effect(_iface, prop, **_kwargs):
values = {
"Type": connection_type,
"State": state,
}
return values[prop]
connection.Get.side_effect = get_side_effect
return connection
def test_reboot_modem_falls_back_to_direct_at(monkeypatch, mocker):
device = Tici()
direct_runner = mocker.MagicMock()
monkeypatch.setattr(device, "get_modem", mocker.MagicMock(side_effect=ModuleNotFoundError("dbus")))
monkeypatch.setattr(device, "_run_direct_modem_command", direct_runner)
device.reboot_modem()
assert direct_runner.call_args_list == [
(("AT+CFUN=0",), {}),
(("AT+CFUN=1",), {}),
]
def test_get_network_type_ignores_non_activated_cellular(mocker):
device = Tici()
primary = _make_connection(mocker, "gsm", NMActiveConnectionState.ACTIVATING)
bus = mocker.MagicMock()
bus.get_object.return_value = primary
nm = mocker.MagicMock()
nm.Get.return_value = "/primary"
device.__dict__["bus"] = bus
device.__dict__["nm"] = nm
assert device.get_network_type() == log.DeviceState.NetworkType.none
def test_get_network_type_requires_registered_modem(monkeypatch, mocker):
device = Tici()
primary = _make_connection(mocker, "gsm", NMActiveConnectionState.ACTIVATED)
cellular = _make_connection(mocker, "gsm", NMActiveConnectionState.ACTIVATED)
bus = mocker.MagicMock()
bus.get_object.side_effect = [primary, cellular]
nm = mocker.MagicMock()
nm.Get.side_effect = ["/primary", ["/cellular"]]
modem = mocker.MagicMock()
def modem_get_side_effect(_iface, prop, **_kwargs):
values = {
"State": MM_MODEM_STATE.SEARCHING,
"AccessTechnologies": MM_MODEM_ACCESS_TECHNOLOGY_LTE,
}
return values[prop]
modem.Get.side_effect = modem_get_side_effect
device.__dict__["bus"] = bus
device.__dict__["nm"] = nm
monkeypatch.setattr(device, "get_modem", mocker.MagicMock(return_value=modem))
assert device.get_network_type() == log.DeviceState.NetworkType.none
def test_get_network_type_reports_lte_for_registered_modem(monkeypatch, mocker):
device = Tici()
primary = _make_connection(mocker, "gsm", NMActiveConnectionState.ACTIVATED)
cellular = _make_connection(mocker, "gsm", NMActiveConnectionState.ACTIVATED)
bus = mocker.MagicMock()
bus.get_object.side_effect = [primary, cellular]
nm = mocker.MagicMock()
nm.Get.side_effect = ["/primary", ["/cellular"]]
modem = mocker.MagicMock()
def modem_get_side_effect(_iface, prop, **_kwargs):
values = {
"State": MM_MODEM_STATE.CONNECTED,
"AccessTechnologies": MM_MODEM_ACCESS_TECHNOLOGY_LTE,
}
return values[prop]
modem.Get.side_effect = modem_get_side_effect
device.__dict__["bus"] = bus
device.__dict__["nm"] = nm
monkeypatch.setattr(device, "get_modem", mocker.MagicMock(return_value=modem))
assert device.get_network_type() == log.DeviceState.NetworkType.cell4G

View File

@@ -0,0 +1,53 @@
import pytest
import numpy as np
from iqpilot.system.hardware.tici import qr_decode as qr_decode_module
from iqpilot.system.hardware.tici.lpa import parse_lpa_activation_code
from iqpilot.system.hardware.tici.qr_decode import validate_lpa_activation_code
def test_parse_valid_activation_code():
version, smdp, matching = parse_lpa_activation_code("LPA:1$rsp.truphone.com$QRF-BETTERROAMING")
assert version == "1"
assert smdp == "rsp.truphone.com"
assert matching == "QRF-BETTERROAMING"
@pytest.mark.parametrize("code", [
"",
"foo",
"LPA:2$rsp.truphone.com$abc",
"LPA:1$$abc",
"LPA:1$rsp.truphone.com$",
"LPA:1$rsp.truphone.com",
])
def test_parse_invalid_activation_code(code):
with pytest.raises(ValueError):
parse_lpa_activation_code(code)
def test_qr_validator_valid():
valid, reason = validate_lpa_activation_code("LPA:1$rsp.truphone.com$QRF-123")
assert valid
assert reason == ""
def test_qr_validator_invalid():
valid, reason = validate_lpa_activation_code("https://example.com")
assert not valid
assert reason
def test_decode_qr_prefers_pyzbar(monkeypatch):
class FakeResult:
data = b"LPA:1$rsp.truphone.com$QRF-123"
monkeypatch.setattr(qr_decode_module, "_pyzbar_decode", lambda arr: [FakeResult()])
def fail_load_decoder():
raise AssertionError("quirc fallback should not be used when pyzbar succeeds")
monkeypatch.setattr(qr_decode_module, "_load_decoder", fail_load_decoder)
payloads = qr_decode_module.decode_qr(np.zeros((4, 4), dtype=np.uint8))
assert payloads == ["LPA:1$rsp.truphone.com$QRF-123"]

View File

@@ -0,0 +1,128 @@
from collections import defaultdict, deque
import pytest
import time
import numpy as np
from dataclasses import dataclass
from tabulate import tabulate
import iqpilot.cereal.messaging as messaging
from iqpilot.cereal.services import SERVICE_LIST
from iqdbc.car.car_helpers import get_demo_car_params
from iqpilot.common.mock import mock_messages
from iqpilot.common.params import Params
from iqpilot.system.hardware.tici.power_monitor import get_power
from iqpilot.system.manager.process_config import managed_processes
from iqpilot.system.manager.manager import manager_cleanup
SAMPLE_TIME = 8 # seconds to sample power
MAX_WARMUP_TIME = 30 # seconds to wait for SAMPLE_TIME consecutive valid samples
@dataclass
class Proc:
procs: list[str]
power: float
msgs: list[str]
rtol: float = 0.05
atol: float = 0.12
@property
def name(self):
return '+'.join(self.procs)
PROCS = [
Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']),
Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']),
Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']),
Proc(['encoderd'], 0.23, msgs=[]),
]
@pytest.mark.tici
class TestPowerDraw:
def setup_method(self):
Params().put("CarParams", get_demo_car_params().to_bytes())
# wait a bit for power save to disable
time.sleep(5)
def teardown_method(self):
manager_cleanup()
def get_expected_messages(self, proc):
return int(sum(SAMPLE_TIME * SERVICE_LIST[msg].frequency for msg in proc.msgs))
def valid_msg_count(self, proc, msg_counts):
msgs_received = sum(msg_counts[msg] for msg in proc.msgs)
msgs_expected = self.get_expected_messages(proc)
return np.isclose(msgs_expected, msgs_received, rtol=.02, atol=2)
def valid_power_draw(self, proc, used):
return np.isclose(used, proc.power, rtol=proc.rtol, atol=proc.atol)
def tabulate_msg_counts(self, msgs_and_power):
msg_counts = defaultdict(int)
for _, counts in msgs_and_power:
for msg, count in counts.items():
msg_counts[msg] += count
return msg_counts
def get_power_with_warmup_for_target(self, proc, prev):
socks = {msg: messaging.sub_sock(msg) for msg in proc.msgs}
for sock in socks.values():
messaging.drain_sock_raw(sock)
msgs_and_power = deque([], maxlen=SAMPLE_TIME)
start_time = time.monotonic()
while (time.monotonic() - start_time) < MAX_WARMUP_TIME:
power = get_power(1)
iteration_msg_counts = {}
for msg,sock in socks.items():
iteration_msg_counts[msg] = len(messaging.drain_sock_raw(sock))
msgs_and_power.append((power, iteration_msg_counts))
if len(msgs_and_power) < SAMPLE_TIME:
continue
msg_counts = self.tabulate_msg_counts(msgs_and_power)
now = np.mean([m[0] for m in msgs_and_power])
if self.valid_msg_count(proc, msg_counts) and self.valid_power_draw(proc, now - prev):
break
return now, msg_counts, time.monotonic() - start_time - SAMPLE_TIME
@mock_messages(['deviceMotion'])
def test_camera_procs(self, subtests):
baseline = get_power()
prev = baseline
used = {}
warmup_time = {}
msg_counts = {}
for proc in PROCS:
for p in proc.procs:
managed_processes[p].start()
now, local_msg_counts, warmup_time[proc.name] = self.get_power_with_warmup_for_target(proc, prev)
msg_counts.update(local_msg_counts)
used[proc.name] = now - prev
prev = now
manager_cleanup()
tab = [['process', 'expected (W)', 'measured (W)', '# msgs expected', '# msgs received', "warmup time (s)"]]
for proc in PROCS:
cur = used[proc.name]
expected = proc.power
msgs_received = sum(msg_counts[msg] for msg in proc.msgs)
tab.append([proc.name, round(expected, 2), round(cur, 2), self.get_expected_messages(proc), msgs_received, round(warmup_time[proc.name], 2)])
with subtests.test(proc=proc.name):
assert self.valid_msg_count(proc, msg_counts), f"expected {self.get_expected_messages(proc)} msgs, got {msgs_received} msgs"
assert self.valid_power_draw(proc, cur), f"expected {expected:.2f}W, got {cur:.2f}W"
print(tabulate(tab))
print(f"Baseline {baseline:.2f}W\n")