firestarter 3.2.7b1__tar.gz → 3.2.7b2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {firestarter-3.2.7b1/firestarter.egg-info → firestarter-3.2.7b2}/PKG-INFO +1 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/_version.py +3 -3
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/cli_handlers.py +36 -6
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/eprom_operations.py +15 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/exceptions.py +3 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/firmware.py +3 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/fw_release_gate.py +46 -4
- {firestarter-3.2.7b1 → firestarter-3.2.7b2/firestarter.egg-info}/PKG-INFO +1 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/scm_version.json +2 -2
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/__snapshots__/test_characterization.ambr +5 -8
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_characterization.py +33 -16
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_cli_handlers.py +8 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_eprom_operations.py +14 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_port_targeting_and_blind_install.py +7 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_release_gate.py +111 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_update_path_gate.py +7 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_py32_channel_gating.py +51 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_py32_dfu.py +7 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/LICENSE +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/MANIFEST.in +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/README.md +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/__init__.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/address_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/avr_tool.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/channel.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/chip_resolver.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/chip_test.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/codec.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/compare.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/config.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/constants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/data/chip_database.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/data/pinouts.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/database.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/diagnostic_report.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/eprom_info.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/erase_support.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/frame_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/hardware.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/ic_layout.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/jp5_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/jumper_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/lock_status.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/log_capture.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/logging_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/main.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/messages.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/page_size_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/protection_readability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/py32_dfu.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/sdp_capability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/sdp_honesty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/serial_comm.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/submit.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/timestamps.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/transport_counters.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/vpp_display.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter/write_blank_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/SOURCES.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/dependency_links.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/entry_points.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/requires.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/scm_file_list.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/firestarter.egg-info/top_level.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/pyproject.toml +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/setup.cfg +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/__init__.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/conftest.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fake_chip.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/part_number_delta.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/report_shapes.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/at28c256-full-all-ok-sdp.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/attr01-status-axis-transport-fault.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/gh20-at28c256-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/gh23-w27e257-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/gh28-m27c512-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/gh47-sst27sf512-pass.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/m27c512-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/m27c512-full-blank-check-bad.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/m27c512-full-canonical-name.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/m27c512-full-comma-joined-name.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/m27c512-full-runs-1.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/prune03-synthesized-fingerprint-match.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/sst27sf512-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/sst27sf512-six-step-readback-gated.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/sst27sf512-six-step.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/synthetic-arm4-empty-results.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/synthetic-arm4-no-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/uv-slot-write-pass.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/reports/w27e257-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/shape_ids.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/fixtures/synthetic_nonzero_chip_id.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/golden/build_db_part_specific_constants.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/golden/chip_database_field_inventory.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/golden/wire_dict.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/plan_corpus.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_address_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_b15_page_size_corroboration.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_blast_radius_invariance.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_boot_block_hint.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_budget_failure_render.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_bug_characterization.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_build_db_constant_census.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_build_db_inclusion.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_build_db_interpret_timing.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_build_db_pinout_fork.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_build_db_sram_non_volatile.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_canonical_part_number.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_database_field_inventory.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_name_resolution.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_resolver.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test_blank_check_order.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test_cycle.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test_sdp_leg.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test_timing.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_chip_test_uv_slot_write.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_click_group_gate_hook.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_cobs.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_codec.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_codec_format_message.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_compare.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_config.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_config_band_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_connect_cost_harness.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_consistency_check.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_coverage_floor_v18.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_database_conversion.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_datasheet_overrides.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_decoder.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_derive_plan_no_drop_sweep.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_derive_plan_structural_sentinel.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_dev_group_channel_gating.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_dev_test_cmd.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_dev_tools_channel_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_devtest_firmware_error_propagation.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_dfu_opcode_anchors.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_diagnostic_report.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_endpoint_constants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_eprom_database.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_eprom_info.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_erase_blank_step_nonregression.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_erase_flag_invariants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_error_code_seam.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_even_block.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_extra_chips_supplement.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_firmware_install.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_list_failure_vs_empty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_update_dead_endpoint.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_version_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fwguard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_hardware.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_hw_revision_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_ic_layout.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_info_vpp_and_erase.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_jp5_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_jumper_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_lock_status_class_partition.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_lock_status_cli.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_lock_status_wire.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_log_capture.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_logging_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_matrix_artifact.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_page_size_alignment_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_page_size_invariants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_page_size_write_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_parse_devtest_issue.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_probe_spurious_setup_ack.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_programming_vcc_census.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protect_flags_doc_measurements.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protection_resolution.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protection_status_catalog.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protection_table_citations.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protocol_not_implemented.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_protocol_not_implemented_production_path.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_provenance.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_pulse_us_override.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_py32_flash_map_host.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_py32_pyusb_absent.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_pyusb_api_surface.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_sdp_capability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_sdp_db_invariant.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_sdp_honesty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_serial_characterization.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_serial_comm.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_session_lease.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_setup_refusals.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_submit.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_timestamps.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_transport_counters.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_uv_mask.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_validate_family_cmd.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_validate_oracle.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_validated_parts_regression_surface.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_vcc_margin_rail.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_vpp_decode_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_vpp_rail_classification.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_wire_dict_equivalence.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_blank_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_blank_guard_pinning.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_guard_port_pinning.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_progress.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_response_budget.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_skip_erase_0x0d.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_skip_sdp_unlock.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_write_verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firestarter
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.7b2
|
|
4
4
|
Summary: EPROM programmer software for the Relatively Universal ROM Programmer
|
|
5
5
|
Author-email: Henrik Olsson <henols@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/henols/firestarter_app
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '3.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (3, 2, 7, '
|
|
21
|
+
__version__ = version = '3.2.7b2'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 2, 7, 'b2')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g30e9def'
|
|
@@ -37,6 +37,7 @@ from firestarter.channel import (
|
|
|
37
37
|
available_boards,
|
|
38
38
|
dev_command_gate_message,
|
|
39
39
|
is_dev_tools_enabled,
|
|
40
|
+
is_prerelease_build,
|
|
40
41
|
)
|
|
41
42
|
from firestarter.chip_resolver import (
|
|
42
43
|
describe_ambiguity,
|
|
@@ -186,6 +187,27 @@ def _complete_eprom(
|
|
|
186
187
|
_ALL_BOARDS: tuple[str, ...] = ("uno", "uno328pb", "leonardo", "py32f071")
|
|
187
188
|
_BOARD_CHOICES: list[str] = available_boards(_ALL_BOARDS)
|
|
188
189
|
_PY32_ENABLED: bool = "py32f071" in _BOARD_CHOICES
|
|
190
|
+
# A stable build never offers pre-release firmware: `fw --pre` is refused and
|
|
191
|
+
# `fw --list` shows stable releases only. fw_release_gate refuses the install.
|
|
192
|
+
_PRE_CHANNEL_ENABLED: bool = is_prerelease_build()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _reject_gated_option(name: str, given: bool, enabled: bool) -> None:
|
|
196
|
+
"""The one refusal for a CLI option that this build's channel does not offer.
|
|
197
|
+
|
|
198
|
+
It reads like Click's own unknown-option error, because on this build the
|
|
199
|
+
option does not exist.
|
|
200
|
+
"""
|
|
201
|
+
if given and not enabled:
|
|
202
|
+
raise click.UsageError(f"no such option: {name}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _reject_pre_only_option(name: str, given: bool) -> None:
|
|
206
|
+
"""Refuse a pre-release-only CLI option on a stable build.
|
|
207
|
+
|
|
208
|
+
Reads `_PRE_CHANNEL_ENABLED` at CALL time, like `_reject_py32_only_option`.
|
|
209
|
+
"""
|
|
210
|
+
_reject_gated_option(name, given, _PRE_CHANNEL_ENABLED)
|
|
189
211
|
|
|
190
212
|
|
|
191
213
|
def _reject_py32_only_option(name: str, given: bool) -> None:
|
|
@@ -204,8 +226,7 @@ def _reject_py32_only_option(name: str, given: bool) -> None:
|
|
|
204
226
|
default -- which is what makes it monkeypatchable while the Click surface
|
|
205
227
|
stays frozen at import time.
|
|
206
228
|
"""
|
|
207
|
-
|
|
208
|
-
raise click.UsageError(f"no such option: {name}")
|
|
229
|
+
_reject_gated_option(name, given, _PY32_ENABLED)
|
|
209
230
|
|
|
210
231
|
|
|
211
232
|
def map_typed_errors(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -1764,6 +1785,7 @@ def _maybe_auto_route_to_pre_click(
|
|
|
1764
1785
|
@click.option(
|
|
1765
1786
|
"--pre",
|
|
1766
1787
|
is_flag=True,
|
|
1788
|
+
hidden=not _PRE_CHANNEL_ENABLED,
|
|
1767
1789
|
help="Fetch latest pre-release firmware (mirrors pip install --pre).",
|
|
1768
1790
|
)
|
|
1769
1791
|
@click.option(
|
|
@@ -1772,7 +1794,11 @@ def _maybe_auto_route_to_pre_click(
|
|
|
1772
1794
|
type=_FirmwareVersionType(),
|
|
1773
1795
|
default=None,
|
|
1774
1796
|
metavar="VERSION",
|
|
1775
|
-
help=
|
|
1797
|
+
help=(
|
|
1798
|
+
"Pin exact firmware version (e.g. 3.1.0, 3.1.0b2, 3.1.0rc1)."
|
|
1799
|
+
if _PRE_CHANNEL_ENABLED
|
|
1800
|
+
else "Pin exact firmware version (e.g. 3.1.0)."
|
|
1801
|
+
),
|
|
1776
1802
|
)
|
|
1777
1803
|
@click.option(
|
|
1778
1804
|
"--stable",
|
|
@@ -1861,11 +1887,15 @@ def fw(
|
|
|
1861
1887
|
) -> None:
|
|
1862
1888
|
"""Firmware version.
|
|
1863
1889
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1890
|
+
The release channel options are mutually exclusive; passing more than one
|
|
1891
|
+
is refused before anything is installed.
|
|
1866
1892
|
"""
|
|
1867
1893
|
app: AppContext = ctx.obj
|
|
1868
1894
|
|
|
1895
|
+
# Before the mutex check, so a stable build never names --pre in an error
|
|
1896
|
+
# other than this one.
|
|
1897
|
+
_reject_pre_only_option("--pre", pre)
|
|
1898
|
+
|
|
1869
1899
|
# 3-way --pre / --firmware-version / --stable mutex enforced once,
|
|
1870
1900
|
# post-parse, with a deterministic error message that doesn't depend on
|
|
1871
1901
|
# the user's option ordering. Raises click.UsageError → exit-2, matching
|
|
@@ -1914,7 +1944,7 @@ def fw(
|
|
|
1914
1944
|
channel_filter: Literal["all", "pre", "stable"]
|
|
1915
1945
|
if pre:
|
|
1916
1946
|
channel_filter = "pre"
|
|
1917
|
-
elif stable:
|
|
1947
|
+
elif stable or not _PRE_CHANNEL_ENABLED:
|
|
1918
1948
|
channel_filter = "stable"
|
|
1919
1949
|
else:
|
|
1920
1950
|
channel_filter = "all"
|
|
@@ -362,6 +362,20 @@ def _blank_expected_bytes(_offset: int, length: int) -> bytes:
|
|
|
362
362
|
return b"\xff" * length
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
def _default_read_path(eprom_name: str) -> str:
|
|
366
|
+
"""The output file name `read` uses when the user gives none:
|
|
367
|
+
`<NAME>.bin`, or `<NAME>-<n>.bin` with the lowest free `n` when that
|
|
368
|
+
file exists, so a second read never overwrites the first.
|
|
369
|
+
"""
|
|
370
|
+
base = eprom_name.upper()
|
|
371
|
+
path = f"{base}.bin"
|
|
372
|
+
n = 1
|
|
373
|
+
while os.path.exists(path):
|
|
374
|
+
path = f"{base}-{n}.bin"
|
|
375
|
+
n += 1
|
|
376
|
+
return path
|
|
377
|
+
|
|
378
|
+
|
|
365
379
|
def _write_blank_guard_refusal_message(
|
|
366
380
|
eprom_name: str, region_start: int, result: CompareResult
|
|
367
381
|
) -> str:
|
|
@@ -1298,7 +1312,7 @@ class EpromOperator:
|
|
|
1298
1312
|
if not cmd_data:
|
|
1299
1313
|
return False
|
|
1300
1314
|
|
|
1301
|
-
actual_output_file = output_file or
|
|
1315
|
+
actual_output_file = output_file or _default_read_path(eprom_name)
|
|
1302
1316
|
logger.info(
|
|
1303
1317
|
f"Reading EPROM {eprom_name.upper()}, saving to {actual_output_file}"
|
|
1304
1318
|
)
|
|
@@ -181,6 +181,9 @@ class FirmwareReleaseRefusedError(FirmwareOperationError):
|
|
|
181
181
|
waives it; `--force` does not, because `--force` means "reinstall the same
|
|
182
182
|
version" and is also a wire flag (FLAG_FORCE).
|
|
183
183
|
|
|
184
|
+
Also raised when this CLI is a stable release and the firmware release is a
|
|
185
|
+
pre-release. Nothing waives that refusal.
|
|
186
|
+
|
|
184
187
|
A FirmwareOperationError, so cli_handlers.map_typed_errors renders it
|
|
185
188
|
verbatim through the existing FirmwareOperationError arm. No new arm.
|
|
186
189
|
"""
|
|
@@ -866,6 +866,9 @@ class FirmwareManager:
|
|
|
866
866
|
# onto the pre channel, which is the channel most likely to be ahead of
|
|
867
867
|
# it. That raises how often this refusal fires. Do not "fix" the auto
|
|
868
868
|
# route to avoid the refusal -- the refusal is the point.
|
|
869
|
+
#
|
|
870
|
+
# The same call refuses a pre-release on a stable CLI. That covers a
|
|
871
|
+
# library caller that asks a stable CLI for channel="pre".
|
|
869
872
|
if app_version is None:
|
|
870
873
|
# Dereferenced at call time, not bound at import, so a test can
|
|
871
874
|
# monkeypatch firestarter.__version__ without reloading this module.
|
|
@@ -16,6 +16,11 @@ The rule is the FEATURE version, (major, minor). The patch number is ignored,
|
|
|
16
16
|
and so is the pre-release suffix: 3.1.0b2 and 3.1.0b5 are one feature version.
|
|
17
17
|
A release is refused when its feature version is ABOVE this CLI's.
|
|
18
18
|
|
|
19
|
+
A stable CLI also refuses every pre-release firmware. A stable CLI is a final
|
|
20
|
+
PEP 440 release; a pre-release firmware is a b/rc/dev tag. This rule ignores the
|
|
21
|
+
feature version, and nothing waives it: a stable user gets stable firmware only.
|
|
22
|
+
A pre-release CLI (a b/rc tag, or a dev checkout) installs either.
|
|
23
|
+
|
|
19
24
|
The reverse pairing -- a CLI newer than the firmware -- is NOT gated here.
|
|
20
25
|
`_maybe_auto_route_to_pre` in cli_handlers.py steers channel selection away from
|
|
21
26
|
it. The two checks cover opposite directions and neither replaces the other.
|
|
@@ -23,7 +28,7 @@ it. The two checks cover opposite directions and neither replaces the other.
|
|
|
23
28
|
Polarity: fail closed. A false pass flashes a board this host cannot drive, and
|
|
24
29
|
the board must then be recovered through the bootloader. A false refusal only
|
|
25
30
|
makes the install unavailable, and `firestarter fw --list` still enumerates
|
|
26
|
-
|
|
31
|
+
the releases. A version that cannot be read on either side is refused exactly
|
|
27
32
|
like a version that is too new.
|
|
28
33
|
|
|
29
34
|
One deliberate exception: a release version of None is NOT a refusal. With no
|
|
@@ -31,7 +36,8 @@ release there is nothing to flash, and `manage_firmware_update` already reports
|
|
|
31
36
|
the failed fetch. Turning a GitHub outage into a version-mismatch refusal would
|
|
32
37
|
be a lie. This is the one place the gate looks fail-open and is not.
|
|
33
38
|
|
|
34
|
-
Escape: `--allow-newer-firmware`. Never an
|
|
39
|
+
Escape for the feature-version refusal: `--allow-newer-firmware`. Never an
|
|
40
|
+
environment variable. The pre-release refusal has no escape.
|
|
35
41
|
|
|
36
42
|
Unlike the pre-serial gates, the authoritative call runs AFTER the release
|
|
37
43
|
metadata fetch, because the release tag is only known then. It still runs before
|
|
@@ -75,6 +81,15 @@ _UNREADABLE_FORMAT = (
|
|
|
75
81
|
"--allow-newer-firmware. This pairing is not tested."
|
|
76
82
|
)
|
|
77
83
|
|
|
84
|
+
_PRERELEASE_ON_STABLE_FORMAT = (
|
|
85
|
+
"Firmware {release} is a pre-release. This CLI is {app}, a stable release. "
|
|
86
|
+
"A stable CLI installs stable firmware only. "
|
|
87
|
+
"Refusing to install firmware {release}.\n"
|
|
88
|
+
"Run 'firestarter fw --install' to install the latest stable firmware.\n"
|
|
89
|
+
"To use pre-release firmware, install a pre-release CLI. "
|
|
90
|
+
"Run 'pip install --upgrade --pre firestarter'."
|
|
91
|
+
)
|
|
92
|
+
|
|
78
93
|
_UNREADABLE_TEXT = "an unreadable version"
|
|
79
94
|
|
|
80
95
|
|
|
@@ -94,6 +109,20 @@ def feature_version(raw: str | None) -> tuple[int, int] | None:
|
|
|
94
109
|
return (parsed.major, parsed.minor)
|
|
95
110
|
|
|
96
111
|
|
|
112
|
+
def is_prerelease_version(raw: str | None) -> bool | None:
|
|
113
|
+
"""True for a PEP 440 pre-release (b, rc or dev), False for a final release.
|
|
114
|
+
|
|
115
|
+
None when the version cannot be read. The caller decides what that means;
|
|
116
|
+
the feature-version check already refuses an unreadable version.
|
|
117
|
+
"""
|
|
118
|
+
if not raw:
|
|
119
|
+
return None
|
|
120
|
+
try:
|
|
121
|
+
return Version(raw).is_prerelease
|
|
122
|
+
except InvalidVersion:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
97
126
|
def is_refused(app_version: str | None, release_version: str | None) -> bool:
|
|
98
127
|
"""True when this CLI must not flash this release.
|
|
99
128
|
|
|
@@ -117,9 +146,22 @@ def require_installable_release(
|
|
|
117
146
|
) -> None:
|
|
118
147
|
"""Raise FirmwareReleaseRefusedError when this CLI must not flash this release.
|
|
119
148
|
|
|
120
|
-
Returns None on pass. `allow_newer` waives the refusal; it
|
|
121
|
-
so a library caller that does not ask for the waiver does
|
|
149
|
+
Returns None on pass. `allow_newer` waives the feature-version refusal; it
|
|
150
|
+
defaults to False so a library caller that does not ask for the waiver does
|
|
151
|
+
not get it. Nothing waives the pre-release refusal on a stable CLI.
|
|
152
|
+
|
|
153
|
+
An unreadable version on either side is not judged by the pre-release rule.
|
|
154
|
+
The feature-version check refuses it.
|
|
122
155
|
"""
|
|
156
|
+
if (
|
|
157
|
+
is_prerelease_version(app_version) is False
|
|
158
|
+
and is_prerelease_version(release_version) is True
|
|
159
|
+
):
|
|
160
|
+
raise FirmwareReleaseRefusedError(
|
|
161
|
+
_PRERELEASE_ON_STABLE_FORMAT.format(
|
|
162
|
+
app=app_version, release=release_version
|
|
163
|
+
)
|
|
164
|
+
)
|
|
123
165
|
if allow_newer or not is_refused(app_version, release_version):
|
|
124
166
|
return
|
|
125
167
|
app = feature_version(app_version)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firestarter
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.7b2
|
|
4
4
|
Summary: EPROM programmer software for the Relatively Universal ROM Programmer
|
|
5
5
|
Author-email: Henrik Olsson <henols@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/henols/firestarter_app
|
|
@@ -217,8 +217,8 @@
|
|
|
217
217
|
|
|
218
218
|
Firmware version.
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
The release channel options are mutually exclusive; passing more than one is
|
|
221
|
+
refused before anything is installed.
|
|
222
222
|
|
|
223
223
|
Options:
|
|
224
224
|
-i, --install Try to install the latest firmware.
|
|
@@ -257,15 +257,12 @@
|
|
|
257
257
|
|
|
258
258
|
Firmware version.
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
The release channel options are mutually exclusive; passing more than one is
|
|
261
|
+
refused before anything is installed.
|
|
262
262
|
|
|
263
263
|
Options:
|
|
264
264
|
-i, --install Try to install the latest firmware.
|
|
265
|
-
--
|
|
266
|
-
pip install --pre).
|
|
267
|
-
--firmware-version VERSION Pin exact firmware version (e.g. 3.1.0,
|
|
268
|
-
3.1.0b2, 3.1.0rc1).
|
|
265
|
+
--firmware-version VERSION Pin exact firmware version (e.g. 3.1.0).
|
|
269
266
|
--stable Explicitly select stable channel. With
|
|
270
267
|
--list, filters to stable releases only.
|
|
271
268
|
--list List available firmware releases for the
|
|
@@ -239,29 +239,36 @@ def _run_fw_help_at_version(version: str) -> str:
|
|
|
239
239
|
firestarter fw [OPTIONS]` header `run_firestarter()`'s real subprocess
|
|
240
240
|
produces, rather than Click's own default derived from `sys.argv[0]`.
|
|
241
241
|
"""
|
|
242
|
+
stdout, stderr, rc = _run_firestarter_at_version(version, "fw", "--help")
|
|
243
|
+
assert rc == 0, stderr
|
|
244
|
+
return stdout
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _run_firestarter_at_version(version: str, *args: str) -> tuple[str, str, int]:
|
|
248
|
+
"""Run `firestarter <args>` in a subprocess simulating `version`.
|
|
249
|
+
|
|
250
|
+
The mechanism of `_run_fw_help_at_version`, for any arguments. Returns
|
|
251
|
+
(normalized_stdout, normalized_stderr, exit_code).
|
|
252
|
+
"""
|
|
242
253
|
program = (
|
|
243
|
-
"import sys
|
|
254
|
+
"import sys\n"
|
|
244
255
|
"import firestarter\n"
|
|
245
256
|
f"firestarter.__version__ = {version!r}\n"
|
|
246
257
|
"from firestarter import cli_handlers\n"
|
|
247
|
-
"
|
|
248
|
-
"with contextlib.redirect_stdout(buf):\n"
|
|
249
|
-
" try:\n"
|
|
250
|
-
" cli_handlers.cli.main(\n"
|
|
251
|
-
" args=['fw', '--help'], prog_name='firestarter'\n"
|
|
252
|
-
" )\n"
|
|
253
|
-
" except SystemExit:\n"
|
|
254
|
-
" pass\n"
|
|
255
|
-
"sys.stdout.write(buf.getvalue())\n"
|
|
258
|
+
f"cli_handlers.cli.main(args={list(args)!r}, prog_name='firestarter')\n"
|
|
256
259
|
)
|
|
257
260
|
result = subprocess.run(
|
|
258
261
|
[sys.executable, "-c", program],
|
|
259
262
|
capture_output=True,
|
|
260
263
|
text=True,
|
|
261
264
|
timeout=15,
|
|
265
|
+
env={**os.environ, "FIRESTARTER_CONFIG_DIR": _CLEAN_CONFIG_DIR},
|
|
266
|
+
)
|
|
267
|
+
return (
|
|
268
|
+
normalize_output(result.stdout),
|
|
269
|
+
normalize_output(result.stderr),
|
|
270
|
+
result.returncode,
|
|
262
271
|
)
|
|
263
|
-
assert result.returncode == 0, result.stderr
|
|
264
|
-
return normalize_output(result.stdout)
|
|
265
272
|
|
|
266
273
|
|
|
267
274
|
def test_help_fw(snapshot):
|
|
@@ -434,15 +441,25 @@ def test_error_write_missing_args(snapshot):
|
|
|
434
441
|
|
|
435
442
|
|
|
436
443
|
def test_error_fw_pre_stable_mutex(snapshot):
|
|
437
|
-
"""fw --pre --stable → mutually exclusive, exit 2.
|
|
438
|
-
|
|
444
|
+
"""fw --pre --stable → mutually exclusive, exit 2.
|
|
445
|
+
|
|
446
|
+
On a pre-release CLI: a stable CLI refuses --pre itself.
|
|
447
|
+
"""
|
|
448
|
+
stdout, stderr, rc = _run_firestarter_at_version(
|
|
449
|
+
"3.0.0b1", "fw", "--pre", "--stable"
|
|
450
|
+
)
|
|
439
451
|
assert rc == 2
|
|
440
452
|
assert stderr == snapshot
|
|
441
453
|
|
|
442
454
|
|
|
443
455
|
def test_error_fw_pre_firmware_version_mutex(snapshot):
|
|
444
|
-
"""fw --pre --firmware-version → mutually exclusive, exit 2.
|
|
445
|
-
|
|
456
|
+
"""fw --pre --firmware-version → mutually exclusive, exit 2.
|
|
457
|
+
|
|
458
|
+
On a pre-release CLI: a stable CLI refuses --pre itself.
|
|
459
|
+
"""
|
|
460
|
+
stdout, stderr, rc = _run_firestarter_at_version(
|
|
461
|
+
"3.0.0b1", "fw", "--pre", "--firmware-version", "3.0.0"
|
|
462
|
+
)
|
|
446
463
|
assert rc == 2
|
|
447
464
|
assert stderr == snapshot
|
|
448
465
|
|
|
@@ -1296,6 +1296,13 @@ def test_fw_install_returns_false(runner: CliRunner) -> None:
|
|
|
1296
1296
|
assert result.exit_code == 1
|
|
1297
1297
|
|
|
1298
1298
|
|
|
1299
|
+
@pytest.fixture
|
|
1300
|
+
def _pre_channel_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
1301
|
+
"""A pre-release build: a stable build refuses --pre before the mutex check."""
|
|
1302
|
+
monkeypatch.setattr(cli_handlers_mod, "_PRE_CHANNEL_ENABLED", True)
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
@pytest.mark.usefixtures("_pre_channel_enabled")
|
|
1299
1306
|
def test_fw_mutex_pre_and_firmware_version(runner: CliRunner) -> None:
|
|
1300
1307
|
"""TRAP #4 / D-13.4: --pre + --firmware-version exits 2 (mutually exclusive).
|
|
1301
1308
|
|
|
@@ -1312,6 +1319,7 @@ def test_fw_mutex_pre_and_firmware_version(runner: CliRunner) -> None:
|
|
|
1312
1319
|
assert "mutually exclusive" in result.output.lower()
|
|
1313
1320
|
|
|
1314
1321
|
|
|
1322
|
+
@pytest.mark.usefixtures("_pre_channel_enabled")
|
|
1315
1323
|
def test_fw_mutex_stable_and_pre(runner: CliRunner) -> None:
|
|
1316
1324
|
"""TRAP #4 / D-13.4: --stable + --pre exits 2 (mutually exclusive)."""
|
|
1317
1325
|
fw_mgr = Mock(spec=FirmwareManager)
|
|
@@ -24,7 +24,7 @@ from unittest.mock import patch
|
|
|
24
24
|
|
|
25
25
|
from firestarter.compare import CompareAccumulator
|
|
26
26
|
from firestarter.config import ConfigManager
|
|
27
|
-
from firestarter.eprom_operations import EpromOperator
|
|
27
|
+
from firestarter.eprom_operations import EpromOperator, _default_read_path
|
|
28
28
|
from firestarter.messages import (
|
|
29
29
|
MSG_DATA_CHUNK,
|
|
30
30
|
MSG_END_DONE,
|
|
@@ -2729,3 +2729,16 @@ def test_seen_message_ids_records_decoded_ids_and_stays_bounded(
|
|
|
2729
2729
|
assert unknown_id not in comm.seen_message_ids
|
|
2730
2730
|
assert isinstance(comm.seen_message_ids, set)
|
|
2731
2731
|
assert all(isinstance(i, int) for i in comm.seen_message_ids)
|
|
2732
|
+
|
|
2733
|
+
|
|
2734
|
+
def test_default_read_path_adds_lowest_free_suffix(tmp_path, monkeypatch):
|
|
2735
|
+
"""`read` with no file name never overwrites an earlier read: the
|
|
2736
|
+
default `<NAME>.bin` gets `-1`, `-2`, ... when that name is taken."""
|
|
2737
|
+
monkeypatch.chdir(tmp_path)
|
|
2738
|
+
assert _default_read_path("w27c512") == "W27C512.bin"
|
|
2739
|
+
|
|
2740
|
+
(tmp_path / "W27C512.bin").write_bytes(b"")
|
|
2741
|
+
assert _default_read_path("w27c512") == "W27C512-1.bin"
|
|
2742
|
+
|
|
2743
|
+
(tmp_path / "W27C512-1.bin").write_bytes(b"")
|
|
2744
|
+
assert _default_read_path("w27c512") == "W27C512-2.bin"
|
{firestarter-3.2.7b1 → firestarter-3.2.7b2}/tests/test_fw_port_targeting_and_blind_install.py
RENAMED
|
@@ -229,6 +229,13 @@ def _manager_that_cannot_identify(monkeypatch):
|
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
class TestBlindInstallRequiresExplicitBoard:
|
|
232
|
+
@pytest.fixture(autouse=True)
|
|
233
|
+
def _prerelease_cli(self, monkeypatch):
|
|
234
|
+
"""A pre-release CLI: a stable CLI refuses the pre-release tags below."""
|
|
235
|
+
import firestarter as _pkg
|
|
236
|
+
|
|
237
|
+
monkeypatch.setattr(_pkg, "__version__", "3.0.0b19")
|
|
238
|
+
|
|
232
239
|
@pytest.mark.parametrize(
|
|
233
240
|
("install_flag", "flags", "label"),
|
|
234
241
|
[(True, 0, "--install"), (False, FLAG_FORCE, "--force")],
|
|
@@ -15,7 +15,7 @@ version explicitly or monkeypatches the package attribute, so a release bump
|
|
|
15
15
|
cannot redden this file.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from unittest.mock import Mock
|
|
18
|
+
from unittest.mock import MagicMock, Mock
|
|
19
19
|
|
|
20
20
|
import pytest
|
|
21
21
|
from click.testing import CliRunner
|
|
@@ -28,9 +28,11 @@ from firestarter.exceptions import (
|
|
|
28
28
|
)
|
|
29
29
|
from firestarter.firmware import FirmwareManager
|
|
30
30
|
from firestarter.fw_release_gate import (
|
|
31
|
+
_PRERELEASE_ON_STABLE_FORMAT,
|
|
31
32
|
_REFUSAL_FORMAT,
|
|
32
33
|
_UNREADABLE_FORMAT,
|
|
33
34
|
feature_version,
|
|
35
|
+
is_prerelease_version,
|
|
34
36
|
is_refused,
|
|
35
37
|
require_installable_release,
|
|
36
38
|
)
|
|
@@ -261,3 +263,111 @@ def test_list_still_enumerates_a_too_new_release(
|
|
|
261
263
|
)
|
|
262
264
|
assert result.exit_code == 0
|
|
263
265
|
assert "3.2.0" in result.output
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
# G. A stable CLI refuses every pre-release firmware
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.parametrize(
|
|
274
|
+
("raw", "expected"),
|
|
275
|
+
[
|
|
276
|
+
("3.2.6", False),
|
|
277
|
+
("v3.2.6", False),
|
|
278
|
+
("3.2.7b1", True),
|
|
279
|
+
("3.2.0rc1", True),
|
|
280
|
+
("3.2.7.dev3+gabc1234", True),
|
|
281
|
+
("not-a-version", None),
|
|
282
|
+
("", None),
|
|
283
|
+
(None, None),
|
|
284
|
+
],
|
|
285
|
+
)
|
|
286
|
+
def test_is_prerelease_version(raw, expected) -> None:
|
|
287
|
+
assert is_prerelease_version(raw) is expected
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pytest.mark.parametrize("release_version", ["3.2.7b1", "3.2.0rc1", "3.3.0.dev1"])
|
|
291
|
+
@pytest.mark.parametrize("allow_newer", [False, True])
|
|
292
|
+
def test_stable_cli_refuses_a_prerelease(release_version, allow_newer) -> None:
|
|
293
|
+
"""No waiver: --allow-newer-firmware does not open this refusal."""
|
|
294
|
+
with pytest.raises(FirmwareReleaseRefusedError) as exc:
|
|
295
|
+
require_installable_release("3.2.6", release_version, allow_newer=allow_newer)
|
|
296
|
+
assert str(exc.value) == _PRERELEASE_ON_STABLE_FORMAT.format(
|
|
297
|
+
app="3.2.6", release=release_version
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_stable_cli_refuses_an_older_prerelease() -> None:
|
|
302
|
+
"""The rule ignores the feature version: an older pre-release is refused too."""
|
|
303
|
+
with pytest.raises(FirmwareReleaseRefusedError):
|
|
304
|
+
require_installable_release("3.2.6", "3.1.0b2")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@pytest.mark.parametrize(
|
|
308
|
+
("app_version", "release_version"),
|
|
309
|
+
[
|
|
310
|
+
("3.2.6", "3.2.6"), # stable on stable
|
|
311
|
+
("3.2.6", None), # nothing to flash
|
|
312
|
+
("3.2.7b1", "3.2.7b2"), # pre-release CLI
|
|
313
|
+
("3.2.7.dev3+gabc1234", "3.2.7b1"), # dev checkout
|
|
314
|
+
("3.2.7b1", "3.2.6"), # pre-release CLI, stable firmware
|
|
315
|
+
],
|
|
316
|
+
)
|
|
317
|
+
def test_prerelease_rule_passes(app_version, release_version) -> None:
|
|
318
|
+
require_installable_release(app_version, release_version)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_stable_cli_unreadable_release_keeps_the_unreadable_refusal() -> None:
|
|
322
|
+
with pytest.raises(FirmwareReleaseRefusedError) as exc:
|
|
323
|
+
require_installable_release("3.2.6", "garbage")
|
|
324
|
+
assert "is a pre-release" not in str(exc.value)
|
|
325
|
+
assert "not readable" in str(exc.value)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_pinned_prerelease_on_stable_cli_refuses_before_any_port_or_request(
|
|
329
|
+
runner, fw_manager, monkeypatch
|
|
330
|
+
) -> None:
|
|
331
|
+
monkeypatch.setattr(_pkg, "__version__", "3.2.6")
|
|
332
|
+
result = runner.invoke(
|
|
333
|
+
cli,
|
|
334
|
+
["fw", "--install", "--firmware-version", "3.2.7b1"],
|
|
335
|
+
obj=make_app_context(firmware_manager=fw_manager),
|
|
336
|
+
)
|
|
337
|
+
assert result.exit_code == 1
|
|
338
|
+
assert "is a pre-release" in result.output
|
|
339
|
+
fw_manager.check_current_firmware.assert_not_called()
|
|
340
|
+
fw_manager.manage_firmware_update.assert_not_called()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_pre_channel_on_stable_library_call_refuses_before_download(
|
|
344
|
+
monkeypatch,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""A library caller that asks a stable CLI for channel='pre' is refused."""
|
|
347
|
+
fm = FirmwareManager(config_manager=MagicMock())
|
|
348
|
+
monkeypatch.setattr(
|
|
349
|
+
fm,
|
|
350
|
+
"check_current_firmware",
|
|
351
|
+
lambda **_kw: ("/dev/ttyACM0", "3.2.6", "uno"),
|
|
352
|
+
)
|
|
353
|
+
monkeypatch.setattr(
|
|
354
|
+
fm,
|
|
355
|
+
"fetch_release_info",
|
|
356
|
+
lambda channel="stable", version=None, board="uno": (
|
|
357
|
+
"3.2.7b1",
|
|
358
|
+
"https://example.invalid/uno.hex",
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
monkeypatch.setattr(
|
|
362
|
+
fm, "_download_firmware_file", lambda url: pytest.fail("must not download")
|
|
363
|
+
)
|
|
364
|
+
monkeypatch.setattr(
|
|
365
|
+
fm, "_install_firmware", lambda **kw: pytest.fail("must not flash")
|
|
366
|
+
)
|
|
367
|
+
with pytest.raises(FirmwareReleaseRefusedError):
|
|
368
|
+
fm.manage_firmware_update(
|
|
369
|
+
install_flag=True,
|
|
370
|
+
channel="pre",
|
|
371
|
+
app_version="3.2.6",
|
|
372
|
+
allow_newer_firmware=True,
|
|
373
|
+
)
|
|
@@ -203,6 +203,13 @@ class TestWaiverPlumbing:
|
|
|
203
203
|
class TestUpdateDecisionReachedOnPreCap02Firmware:
|
|
204
204
|
"""The end-to-end regression: the update decision must be REACHED."""
|
|
205
205
|
|
|
206
|
+
@pytest.fixture(autouse=True)
|
|
207
|
+
def _prerelease_cli(self, monkeypatch):
|
|
208
|
+
"""A pre-release CLI: a stable CLI refuses the pre-release tags below."""
|
|
209
|
+
import firestarter as _pkg
|
|
210
|
+
|
|
211
|
+
monkeypatch.setattr(_pkg, "__version__", "3.0.0b19")
|
|
212
|
+
|
|
206
213
|
def test_manage_firmware_update_reaches_the_download_boundary(self, monkeypatch):
|
|
207
214
|
"""No identity in the ack, version from the legacy text ack, then install.
|
|
208
215
|
|