Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions PROVESFlightControllerReference/test/int/mode_manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import pytest
from common import proves_send_and_assert_command
from fprime_gds.common.data_types.event_data import EventData
from fprime_gds.common.models.serialize.time_type import TimeType
from fprime_gds.common.testing_fw.api import IntegrationTestAPI

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -543,6 +544,7 @@ def test_safe_08_clean_reboot_no_safe_mode(

@pytest.mark.slow
@pytest.mark.uart_only(reason="Requires reboot and GDS reconnect")
@pytest.mark.requires_watchdog_jumper
def test_safe_09_command_loss_triggers_safe_mode_and_reboot(
fprime_test_api: IntegrationTestAPI, start_gds
):
Expand Down Expand Up @@ -581,24 +583,37 @@ def test_safe_09_command_loss_triggers_safe_mode_and_reboot(
# Wait for the 1Hz run_handler to detect command loss (at most 2 seconds)
fprime_test_api.assert_event(f"{component}.CommandLossDetected", timeout=5)

# Verify EnteringSafeMode event mentions loss of contact
events = fprime_test_api.get_event_test_history()
entering_events = [
e for e in events if "EnteringSafeMode" in str(e.get_template().get_name())
]
assert len(entering_events) > 0, (
"EnteringSafeMode event should be emitted on command loss"
# Verify EnteringSafeMode event mentions loss of contact. The firmware emits it a few
# tens of ms after CommandLossDetected, so wait for it rather than scraping the history
# snapshot (which races the EVR's arrival at the GDS).
entering_event = fprime_test_api.assert_event(
f"{component}.EnteringSafeMode", timeout=5
)
assert "contact" in entering_events[-1].get_display_text().lower(), (
assert "contact" in entering_event.get_display_text().lower(), (
"EnteringSafeMode should mention loss of contact"
)

# stopWatchdog was called after safe mode entry — hardware reset expected in ~30 seconds
logger.info("Waiting for hardware reboot triggered by watchdog stop (~60s)...")
time.sleep(60.0)
reboot_start: TimeType = TimeType().set_datetime(
datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE")
)
Comment on lines +597 to +599

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the return type of set_datetime and how TimeBase is initialized in fprime_gds.

python -c '
try:
    from fprime_gds.common.models.serialize.time_type import TimeType
    from datetime import datetime, timezone
    
    t = TimeType()
    res = t.set_datetime(datetime.now(timezone.utc), time_base=0)
    print("set_datetime returns:", res)
    
    print("TimeBase type:", type(TimeType.TimeBase))
    # Check if TimeBase can be instantiated with a string
    try:
        tb = TimeType.TimeBase("TB_DONT_CARE")
        print("TimeBase string instantiation succeeded:", tb)
    except Exception as e:
        print("TimeBase string instantiation failed:", type(e).__name__, "-", e)
        try:
            val = TimeType.TimeBase["TB_DONT_CARE"].value
            print("Bracket notation succeeded. Value:", val)
        except Exception as e2:
            print("Bracket notation failed:", e2)
except ImportError as e:
    print("Could not import TimeType:", e)
'

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and locate the TimeType implementation.
git ls-files PROVESFlightControllerReference/test/int/mode_manager_test.py
fd -a 'time_type.py' .
fd -a 'TimeType.py' .
fd -a '*time*type*' . | head -200

# Inspect the relevant test lines.
python3 - <<'PY'
from pathlib import Path
path = Path('PROVESFlightControllerReference/test/int/mode_manager_test.py')
lines = path.read_text().splitlines()
for start in (585, 595, 620):
    end = min(len(lines), start + 35)
    print(f"\n--- {path}:{start+1}-{end} ---")
    for i in range(start, end):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find definitions/usages related to TimeType and TimeBase in the repository.
rg -n "class TimeType|def set_datetime|TimeBase|TB_DONT_CARE" PROVESFlightControllerReference -g '*.py'

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 1626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path('PROVESFlightControllerReference/test/int/mode_manager_test.py')
lines = path.read_text().splitlines()

# Print the surrounding region and any nearby references to reboot_start.
for start in (590, 600, 610, 620, 630):
    end = min(len(lines), start + 20)
    print(f"\n--- {path}:{start+1}-{end} ---")
    for i in range(start, end):
        print(f"{i+1:4d}: {lines[i]}")

print("\n--- reboot_start references ---")
for i, line in enumerate(lines, 1):
    if "reboot_start" in line:
        print(f"{i:4d}: {line}")
PY

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 3639


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Compare the surrounding test patterns for TimeType initialization and timezone handling.
python3 - <<'PY'
from pathlib import Path

for file in [
    'PROVESFlightControllerReference/test/int/camera_handler_test.py',
    'PROVESFlightControllerReference/test/int/tmp112_test.py',
    'PROVESFlightControllerReference/test/int/drv2605_test.py',
    'PROVESFlightControllerReference/test/int/rtc_test.py',
    'PROVESFlightControllerReference/test/int/radio_test.py',
    'PROVESFlightControllerReference/test/int/veml6031_test.py',
    'PROVESFlightControllerReference/test/int/reset_manager_test.py',
    'PROVESFlightControllerReference/test/int/pico_temp_test.py',
]:
    path = Path(file)
    if not path.exists():
        continue
    text = path.read_text().splitlines()
    print(f"\n=== {file} ===")
    for i, line in enumerate(text, 1):
        if 'TimeType.TimeBase("TB_DONT_CARE")' in line or 'datetime.now()' in line or 'datetime.now(timezone.utc)' in line:
            print(f"{i:4d}: {line}")
PY

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 2042


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate any local references to fprime_gds packaging/docs and TimeType usage patterns.
rg -n "fprime_gds|set_datetime\(|TimeType\.TimeBase\(\"TB_DONT_CARE\"\)" -g '!**/.git/**' .

Repository: Open-Source-Space-Foundation/proves-core-reference

Length of output: 10900


🌐 Web query:

fprime_gds TimeType set_datetime return value TimeType.TimeBase TB_DONT_CARE

💡 Result:

In the F Prime (F´) framework, TimeType is a serializable class used to store timestamp information [1]. The value TB_DONT_CARE is defined as a constant (specifically 0xFFFF) within the TimeBase enumeration [2][3][4], which is used to indicate that a sequence or operation should run regardless of the flight software's specific time base [3]. While the F Prime documentation and source code references for the TimeType class (in both C++ and Python GDS) include various setter methods—such as set for time values or setTimeBase [5]—there is no standard method named set_datetime that returns TB_DONT_CARE as a value. The confusion likely stems from the fact that TimeType objects are associated with time bases, and TB_DONT_CARE is a valid value for the time base field itself [2][4]. If you are encountering code that references a return value of TB_DONT_CARE, it is likely referring to the state or configuration of the TimeBase property rather than the return value of a specific set_datetime method. In F Prime, the TimeBase enumeration includes: - TB_NONE: No time base established [2][4] - TB_PROC_TIME: Processor cycle time [2][4] - TB_WORKSTATION_TIME: Workstation time [2][4] - TB_SC_TIME: Spacecraft time [4] - TB_FPGA_TIME: FPGA/hardware clock [4] - TB_DONT_CARE: A wildcard value used for sequences (0xFFFF) [2][3][4]

Citations:


🌐 Web query:

fprime_gds Python TimeType set_datetime TimeBase TB_DONT_CARE enum

💡 Result:

In the F' (F Prime) framework, TimeBase.TB_DONT_CARE is an enumeration member used to indicate that a specific time base requirement should be ignored, typically during sequence execution [1][2][3]. Key details regarding TB_DONT_CARE and the TimeType system include: 1. Usage in Sequencing: TB_DONT_CARE (often with a value of 0xFFFF or 65535) allows sequences to execute regardless of the current flight software time base [1][3]. Other common time bases include TB_PROC_TIME, TB_WORKSTATION_TIME, and TB_SC_TIME [2][4]. 2. TimeType in GDS: The fprime_gds.common.models.serialize.time_type.TimeType class is used to store and manipulate timestamps within the GDS Python environment [5][6]. 3. Initialization: When initializing a TimeType object, the time_base argument expects an integer corresponding to a valid TimeBase enum value [5]. 4. Comparison and Math: While TimeType objects support rich comparison and mathematical operations, operations between objects with different time bases or contexts generally preserve the attributes of the left operand [7][8]. There is no specific method named set_datetime in the standard Python TimeType class provided by fprime_gds; however, the class includes a get_datetime method used to convert a TimeType into a Python datetime object [5]. The time is typically set during the instantiation of the TimeType object or via internal helper methods [5].

Citations:


🌐 Web query:

"TimeType.set_datetime" fprime_gds returns self

💡 Result:

In the fprime-gds framework, the TimeType class does not have a method named set_datetime [1][2]. The documentation for fprime.common.models.serialize.time_type.TimeType lists get_datetime(tz=None) as the method for retrieving a Python datetime object, but it does not include a corresponding set_datetime method [1]. Furthermore, TimeType objects are generally designed to hold timestamp information (seconds and microseconds) and support arithmetic and comparison operations [3][4]. Internal methods such as _TimeTypeset_float exist for internal helper purposes but are not intended for general modification of existing TimeType instances [1]. The confusion may arise from comparing this to other libraries (such as PHP's DateTime::setTime [5]) or misremembering the API structure.

Citations:


Use a UTC-aware timestamp here. assert_event(..., start=...) should be anchored with datetime.now(timezone.utc) to avoid timezone-dependent event matching.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 600-600: datetime.datetime.now() called without a tz argument

(DTZ005)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PROVESFlightControllerReference/test/int/mode_manager_test.py` around lines
599 - 601, Update the reboot_start initialization in mode_manager_test.py to use
a UTC-aware current timestamp via datetime.now(timezone.utc), while preserving
the existing TimeType.set_datetime call and TB_DONT_CARE time base.

Sources: Coding guidelines, Linters/SAST tools

logger.info("Waiting for hardware reboot triggered by watchdog stop...")
fprime_test_api.assert_event(
"CdhCore.version.FrameworkVersion", start=reboot_start, timeout=90
)

# Verify reboot occurred
# Verify reboot occurred. StartupManager increments the boot count lazily
# on its first 1Hz run tick, and FrameworkVersion is emitted during topology
# startup before rate groups run — so immediately after reboot detection
# GET_BOOT_COUNT can still return the pre-reboot count. Retry briefly until
# the increment lands. Asserting +1 (not just "a startup EVR arrived") also
# catches a boot loop: a watchdog that isn't re-fed on the new boot would
# keep resetting and drive the count past initial + 1.
deadline = time.monotonic() + 30.0
final_boot_count = _get_boot_count(fprime_test_api)
while final_boot_count == initial_boot_count and time.monotonic() < deadline:
time.sleep(2.0)
final_boot_count = _get_boot_count(fprime_test_api)
assert final_boot_count == initial_boot_count + 1, (
f"Boot count should increment by 1 after command loss reboot. "
f"Before: {initial_boot_count}, After: {final_boot_count}"
Expand Down
Loading