Skip to content

Make mode manager test more reliable - #460

Open
nateinaction wants to merge 5 commits into
mainfrom
dont-die-mode-manager
Open

Make mode manager test more reliable#460
nateinaction wants to merge 5 commits into
mainfrom
dont-die-mode-manager

Conversation

@nateinaction

Copy link
Copy Markdown
Collaborator

Description

I suspect that the 60 second sleep is maybe too long or short or something. This PR replaces the sleep with an event assert that will, hopefully at a minimum, speed up tests since it can escape prior to 60 seconds of the reset happens sonner than 60 seconds.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 12 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 00fec140-e286-44da-95d8-ee5cad30610d

📥 Commits

Reviewing files that changed from the base of the PR and between b1b4235 and 251a28c.

📒 Files selected for processing (1)
  • PROVESFlightControllerReference/test/int/mode_manager_test.py
📝 Walkthrough

Walkthrough

The safe-mode command-loss integration test now declares its watchdog hardware requirement, detects reboot completion using a timestamped framework-version event, and polls until the boot count reflects the reboot.

Changes

Watchdog reboot test

Layer / File(s) Summary
Watchdog reboot synchronization
PROVESFlightControllerReference/test/int/mode_manager_test.py
Adds TimeType support, marks the test as requiring a watchdog jumper, replaces the fixed sleep with a timestamp-based framework-version event wait, and retries boot-count retrieval until it increments.

Estimated code review effort: 2 (Simple) | ~5 minutes

Poem

I’m a rabbit watching watchdogs leap,
While reboot timestamps gently keep.
Boot counts hop from one to two,
Safe mode waits for proof anew.
Hop, test, hop—the checks come through!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only covers the change rationale and omits required template sections like related issues, testing, checklist, and notes. Add the missing template sections: related issues, how it was tested, checklist items, screenshots if relevant, and any further notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: improving mode manager test reliability.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@PROVESFlightControllerReference/test/int/mode_manager_test.py`:
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9a1c45ea-8c27-4f38-a29e-a7146a7fcb92

📥 Commits

Reviewing files that changed from the base of the PR and between 3d761fc and f220cbb.

📒 Files selected for processing (1)
  • PROVESFlightControllerReference/test/int/mode_manager_test.py

Comment on lines +599 to +601
reboot_start: TimeType = TimeType().set_datetime(
datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE")
)

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

Mikefly123 and others added 2 commits July 21, 2026 22:33
…zy increment

The FrameworkVersion reboot-detection assert fires during topology startup,
before StartupManager's first 1Hz run tick performs the lazy boot-count
increment (StartupManager.cpp get_boot_count(true) behind the m_boot_count==0
guard). Querying GET_BOOT_COUNT immediately after reboot detection therefore
races the increment and can read the pre-reboot count — the failure seen in
run 29894110572 (query at TB_PROC_TIME 4.8s, count still 2).

Keep the +1 assertion (it distinguishes exactly-one reboot from a boot loop,
which a startup-EVR assert cannot) but retry the read for up to 30s until the
increment lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second flake in test_safe_09, seen on PR #470's CI (run 29983198334):
CommandLossDetected asserted fine, but the immediate history scrape ran
before EnteringSafeMode (emitted ~40ms later in the same firmware code
path) reached the GDS. Use assert_event with a timeout so the check
waits for the event instead of racing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ineskhou pushed a commit that referenced this pull request Jul 27, 2026
…tion (#470)

* fix(startup): harden boot count persistence against hard-reset corruption

HWIL evidence (integration-uart runs 29894110572 / 29979002023, PR #460):
the boot count file was read back as 0x02FE191005000001 after a sequence
of watchdog-commanded hard resets, and the next boot persisted exactly
garbage+1 - the increment machinery works, but nothing guards against a
torn flash write poisoning the count forever.

Four changes to StartupManager:
- Corruption guard: values > 1,000,000 are treated as a failed read
  (mirrors the existing quiescence-file 0xFF-fill guard) and reported
  via new WARNING_HI BootCountCorrupted with the raw value.
- GET_BOOT_COUNT is now read-only: the unconditional write-back on every
  query was the largest source of boot-count write traffic, and each
  write is a window for a reset to tear the file.
- Increment retry: if the first-tick persist fails (e.g. filesystem not
  ready), run_handler retries each 1Hz tick until it sticks - the
  increment is delayed, not lost. Failure warning de-duplicated per streak.
- Atomic persist: write temp file then Os::FileSystem::rename over the
  target; littlefs renames are atomic so a mid-update reset leaves either
  the old or new file, never a torn one.

Regression seam: test_safe_09_command_loss_triggers_safe_mode_and_reboot
asserts boot count == initial+1 across a watchdog reset (with the retry
poll from PR #460).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(startup): flush before close in file write helper; zero-init m_boot_count

Addresses CodeRabbit review on #470: Os::File::close() is void and cannot
report a deferred flush failure, so the write helper now requires
file.flush() == OP_OK before reporting SUCCESS - persist_boot_count no
longer renames a temp file whose data may not have reached storage.
Also gives m_boot_count an in-class zero initializer; run_handler's
first-tick guard reads it before any assignment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: correct filesystem — flight FS is FAT (ELM FatFs), not littlefs

The persist design claimed littlefs-atomic renames; the flight filesystem
is FAT (CONFIG_FAT_FILESYSTEM_ELM, zephyr,fstab,fatfs). FAT renames are
not guaranteed power-cut atomic. The write-then-rename design still closes
the observed torn-in-place-write window (data fully flushed before it
replaces the old file); the residual worst case is a missing file, which
reads as a failed read and re-initializes visibly instead of propagating
silent garbage. Comments and SDD updated to state this accurately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants