firestarter 3.2.7b2__tar.gz → 3.2.7b3__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.7b2/firestarter.egg-info → firestarter-3.2.7b3}/PKG-INFO +1 -1
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/_version.py +3 -3
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/channel.py +8 -15
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/cli_handlers.py +9 -23
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/eprom_operations.py +33 -14
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/firmware.py +82 -14
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/fw_release_gate.py +45 -11
- {firestarter-3.2.7b2 → firestarter-3.2.7b3/firestarter.egg-info}/PKG-INFO +1 -1
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/scm_version.json +2 -2
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_eprom_operations.py +62 -9
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_firmware_install.py +7 -5
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_port_targeting_and_blind_install.py +2 -2
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_release_gate.py +139 -1
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_update_dead_endpoint.py +2 -2
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_update_path_gate.py +1 -1
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/LICENSE +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/MANIFEST.in +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/README.md +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/__init__.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/address_parser.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/avr_tool.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/chip_resolver.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/chip_test.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/codec.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/compare.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/config.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/constants.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/data/chip_database.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/data/pinouts.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/database.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/diagnostic_report.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/eprom_info.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/erase_support.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/exceptions.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/frame_parser.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/hardware.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/ic_layout.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/jp5_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/jumper_table.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/lock_status.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/log_capture.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/logging_utils.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/main.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/messages.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/page_size_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/protection_readability.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/py32_dfu.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/sdp_capability.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/sdp_honesty.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/serial_comm.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/submit.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/timestamps.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/transport_counters.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/utils.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/vpp_display.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter/write_blank_guard.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/SOURCES.txt +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/dependency_links.txt +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/entry_points.txt +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/requires.txt +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/scm_file_list.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/firestarter.egg-info/top_level.txt +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/pyproject.toml +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/setup.cfg +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/__init__.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/__snapshots__/test_characterization.ambr +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/conftest.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fake_chip.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/part_number_delta.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/report_shapes.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/at28c256-full-all-ok-sdp.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/attr01-status-axis-transport-fault.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/gh20-at28c256-fail.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/gh23-w27e257-fail.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/gh28-m27c512-fail.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/gh47-sst27sf512-pass.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-all-ok.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-blank-check-bad.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-canonical-name.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-comma-joined-name.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-runs-1.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/prune03-synthesized-fingerprint-match.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-full-all-ok.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-six-step-readback-gated.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-six-step.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/synthetic-arm4-empty-results.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/synthetic-arm4-no-ok.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/uv-slot-write-pass.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/reports/w27e257-full-all-ok.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/shape_ids.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/fixtures/synthetic_nonzero_chip_id.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/golden/build_db_part_specific_constants.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/golden/chip_database_field_inventory.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/golden/wire_dict.json +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/plan_corpus.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_address_parser.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_b15_page_size_corroboration.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_blast_radius_invariance.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_boot_block_hint.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_budget_failure_render.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_bug_characterization.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_build_db_constant_census.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_build_db_inclusion.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_build_db_interpret_timing.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_build_db_pinout_fork.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_build_db_sram_non_volatile.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_canonical_part_number.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_characterization.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_database_field_inventory.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_name_resolution.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_resolver.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test_blank_check_order.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test_cycle.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test_sdp_leg.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test_timing.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_chip_test_uv_slot_write.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_cli_handlers.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_click_group_gate_hook.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_cobs.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_codec.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_codec_format_message.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_compare.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_config.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_config_band_refusal.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_connect_cost_harness.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_consistency_check.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_coverage_floor_v18.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_database_conversion.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_datasheet_overrides.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_decoder.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_derive_plan_no_drop_sweep.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_derive_plan_structural_sentinel.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_dev_group_channel_gating.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_dev_test_cmd.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_dev_tools_channel_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_devtest_firmware_error_propagation.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_dfu_opcode_anchors.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_diagnostic_report.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_endpoint_constants.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_eprom_database.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_eprom_info.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_erase_blank_step_nonregression.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_erase_flag_invariants.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_error_code_seam.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_even_block.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_extra_chips_supplement.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_list_failure_vs_empty.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_version_guard.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fwguard.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_hardware.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_hw_revision_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_ic_layout.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_info_vpp_and_erase.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_jp5_gate.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_jumper_table.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_lock_status_class_partition.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_lock_status_cli.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_lock_status_wire.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_log_capture.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_logging_utils.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_matrix_artifact.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_page_size_alignment_refusal.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_page_size_invariants.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_page_size_write_refusal.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_parse_devtest_issue.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_probe_spurious_setup_ack.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_programming_vcc_census.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protect_flags_doc_measurements.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protection_resolution.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protection_status_catalog.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protection_table_citations.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protocol_not_implemented.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_protocol_not_implemented_production_path.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_provenance.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_pulse_us_override.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_py32_channel_gating.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_py32_dfu.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_py32_flash_map_host.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_py32_pyusb_absent.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_pyusb_api_surface.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_sdp_capability.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_sdp_db_invariant.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_sdp_honesty.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_serial_characterization.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_serial_comm.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_session_lease.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_setup_refusals.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_submit.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_timestamps.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_transport_counters.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_utils.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_uv_mask.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_validate_family_cmd.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_validate_oracle.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_validated_parts_regression_surface.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_vcc_margin_rail.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_vpp_decode_table.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_vpp_rail_classification.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_wire_dict_equivalence.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_blank_guard.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_blank_guard_pinning.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_guard_port_pinning.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_progress.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_response_budget.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_skip_erase_0x0d.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_write_skip_sdp_unlock.py +0 -0
- {firestarter-3.2.7b2 → firestarter-3.2.7b3}/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.7b3
|
|
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.7b3'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 2, 7, 'b3')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g85b7152'
|
|
@@ -59,23 +59,16 @@ def is_prerelease_build() -> bool:
|
|
|
59
59
|
"""True when this build came off `beta` (its own version is a pre-release).
|
|
60
60
|
|
|
61
61
|
Fails **closed**: an unparseable version is treated as stable, so a gated
|
|
62
|
-
feature stays hidden rather than leaking.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
feature stays hidden rather than leaking. `_maybe_auto_route_to_pre` uses
|
|
63
|
+
this function, so it treats an unparseable version the same way. In
|
|
64
|
+
practice `__version__` is a literal in the package and is always valid
|
|
65
|
+
PEP 440; dev versions (`2.0.7_dev`) parse as pre-releases and therefore
|
|
66
|
+
keep gated features enabled while working from a checkout.
|
|
67
67
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
import firestarter as _pkg
|
|
69
|
+
from firestarter.fw_release_gate import is_prerelease_version
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
return bool(Version(_pkg.__version__).is_prerelease)
|
|
75
|
-
except InvalidVersion:
|
|
76
|
-
return False
|
|
77
|
-
except ImportError: # pragma: no cover — packaging is a hard dependency
|
|
78
|
-
return False
|
|
71
|
+
return is_prerelease_version(_pkg.__version__) is True
|
|
79
72
|
|
|
80
73
|
|
|
81
74
|
def is_board_available(board: str | None) -> bool:
|
|
@@ -343,29 +343,15 @@ def _maybe_auto_route_to_pre(args: object) -> None:
|
|
|
343
343
|
or getattr(args, "stable", False)
|
|
344
344
|
):
|
|
345
345
|
return
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"Beta app detected — defaulting to --pre. "
|
|
356
|
-
"Use --firmware-version X.Y.Z to pin a stable version."
|
|
357
|
-
)
|
|
358
|
-
except InvalidVersion:
|
|
359
|
-
# Deliberate fail-OPEN, and deliberately the opposite polarity to
|
|
360
|
-
# fw_release_gate, which fails CLOSED on the same unreadable input.
|
|
361
|
-
# The asymmetry is intended: the worst case here is that the wrong
|
|
362
|
-
# channel is picked, which the gate then judges on its merits; the
|
|
363
|
-
# worst case there is a board flashed with firmware this host cannot
|
|
364
|
-
# drive, recoverable only through the bootloader. Do not "align"
|
|
365
|
-
# these two without re-reading both worst cases.
|
|
366
|
-
pass
|
|
367
|
-
except ImportError:
|
|
368
|
-
pass
|
|
346
|
+
# An unreadable version is not a pre-release here: the worst case is the
|
|
347
|
+
# wrong channel, which fw_release_gate then judges and refuses (it fails
|
|
348
|
+
# CLOSED on the same input). Do not "align" the two polarities.
|
|
349
|
+
if is_prerelease_build():
|
|
350
|
+
args.pre = True # type: ignore[attr-defined]
|
|
351
|
+
helper_logger.info(
|
|
352
|
+
"Beta app detected — defaulting to --pre. "
|
|
353
|
+
"Use --firmware-version X.Y.Z to pin a stable version."
|
|
354
|
+
)
|
|
369
355
|
|
|
370
356
|
|
|
371
357
|
def _build_op_flags(
|
|
@@ -16,7 +16,7 @@ import statistics
|
|
|
16
16
|
import time
|
|
17
17
|
from contextlib import contextmanager
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Callable, Dict, Tuple # noqa: UP035
|
|
19
|
+
from typing import BinaryIO, Callable, Dict, Tuple # noqa: UP035
|
|
20
20
|
|
|
21
21
|
import tqdm
|
|
22
22
|
from tqdm.contrib.logging import logging_redirect_tqdm
|
|
@@ -362,18 +362,23 @@ def _blank_expected_bytes(_offset: int, length: int) -> bytes:
|
|
|
362
362
|
return b"\xff" * length
|
|
363
363
|
|
|
364
364
|
|
|
365
|
-
def
|
|
366
|
-
"""
|
|
365
|
+
def _open_default_read_file(eprom_name: str) -> tuple[str, BinaryIO]:
|
|
366
|
+
"""Create the output file `read` uses when the user gives none:
|
|
367
367
|
`<NAME>.bin`, or `<NAME>-<n>.bin` with the lowest free `n` when that
|
|
368
368
|
file exists, so a second read never overwrites the first.
|
|
369
|
+
|
|
370
|
+
Mode 'xb' claims the name and creates the file in one step, so two reads
|
|
371
|
+
that start together never get the same name.
|
|
369
372
|
"""
|
|
370
373
|
base = eprom_name.upper()
|
|
371
374
|
path = f"{base}.bin"
|
|
372
375
|
n = 1
|
|
373
|
-
while
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
376
|
+
while True:
|
|
377
|
+
try:
|
|
378
|
+
return path, open(path, "xb")
|
|
379
|
+
except FileExistsError:
|
|
380
|
+
path = f"{base}-{n}.bin"
|
|
381
|
+
n += 1
|
|
377
382
|
|
|
378
383
|
|
|
379
384
|
def _write_blank_guard_refusal_message(
|
|
@@ -1312,14 +1317,20 @@ class EpromOperator:
|
|
|
1312
1317
|
if not cmd_data:
|
|
1313
1318
|
return False
|
|
1314
1319
|
|
|
1315
|
-
actual_output_file = output_file or
|
|
1316
|
-
|
|
1317
|
-
f"Reading EPROM {eprom_name.upper()}, saving to {actual_output_file}"
|
|
1318
|
-
)
|
|
1319
|
-
start_time = time.time()
|
|
1320
|
-
|
|
1320
|
+
actual_output_file = output_file or ""
|
|
1321
|
+
is_ok = False
|
|
1321
1322
|
try:
|
|
1322
|
-
|
|
1323
|
+
if output_file:
|
|
1324
|
+
file_handle = open(output_file, "wb")
|
|
1325
|
+
else:
|
|
1326
|
+
actual_output_file, file_handle = _open_default_read_file(
|
|
1327
|
+
eprom_name
|
|
1328
|
+
)
|
|
1329
|
+
logger.info(
|
|
1330
|
+
f"Reading EPROM {eprom_name.upper()}, saving to {actual_output_file}"
|
|
1331
|
+
)
|
|
1332
|
+
start_time = time.time()
|
|
1333
|
+
with file_handle:
|
|
1323
1334
|
|
|
1324
1335
|
def _write_to_file(address, data_chunk):
|
|
1325
1336
|
file_handle.seek(address)
|
|
@@ -1340,6 +1351,14 @@ class EpromOperator:
|
|
|
1340
1351
|
except IOError as e: # noqa: UP024
|
|
1341
1352
|
logger.error(f"File I/O error with {actual_output_file}: {e}")
|
|
1342
1353
|
return False
|
|
1354
|
+
finally:
|
|
1355
|
+
# A failed read with a generated name leaves no file behind,
|
|
1356
|
+
# so the next read takes that name again.
|
|
1357
|
+
if not output_file and actual_output_file and not is_ok:
|
|
1358
|
+
try:
|
|
1359
|
+
os.remove(actual_output_file)
|
|
1360
|
+
except OSError:
|
|
1361
|
+
pass
|
|
1343
1362
|
|
|
1344
1363
|
def consistency_check_eprom(
|
|
1345
1364
|
self,
|
|
@@ -39,7 +39,12 @@ from firestarter.exceptions import (
|
|
|
39
39
|
ProgrammerNotFoundError,
|
|
40
40
|
SerialError,
|
|
41
41
|
)
|
|
42
|
-
from firestarter.fw_release_gate import
|
|
42
|
+
from firestarter.fw_release_gate import (
|
|
43
|
+
feature_version,
|
|
44
|
+
is_prerelease_version,
|
|
45
|
+
matches_stable_cli,
|
|
46
|
+
require_installable_release,
|
|
47
|
+
)
|
|
43
48
|
from firestarter.serial_comm import SerialCommunicator
|
|
44
49
|
from firestarter.timestamps import iso_text_to_cet
|
|
45
50
|
|
|
@@ -331,15 +336,21 @@ class FirmwareManager:
|
|
|
331
336
|
channel: Literal["stable", "pre", "pinned"] = "stable",
|
|
332
337
|
version: str | None = None,
|
|
333
338
|
board: str = "uno",
|
|
339
|
+
app_version: str | None = None,
|
|
334
340
|
) -> Tuple[str | None, str | None]: # noqa: UP006
|
|
335
341
|
"""Router: returns (resolved_version, download_url) or (None, None) on failure.
|
|
336
342
|
|
|
337
|
-
channel='stable' →
|
|
343
|
+
channel='stable' → for a stable app_version, the highest stable release on
|
|
344
|
+
its feature version (a stable CLI 3.2.x runs 3.2.y only);
|
|
345
|
+
otherwise delegates to fetch_latest_release_info.
|
|
338
346
|
channel='pre' → paginates /releases, takes the highest non-draft release by
|
|
339
347
|
PEP 440, stable or pre-release; falls back to stable if none.
|
|
340
348
|
channel='pinned' → fetches /releases/tags/{version} directly.
|
|
341
349
|
""" # noqa: E501
|
|
342
350
|
if channel == "stable":
|
|
351
|
+
app_feature = feature_version(app_version)
|
|
352
|
+
if is_prerelease_version(app_version) is False and app_feature:
|
|
353
|
+
return self._fetch_matching_stable_release_info(app_feature, board)
|
|
343
354
|
return self.fetch_latest_release_info(board=board)
|
|
344
355
|
|
|
345
356
|
elif channel == "pinned":
|
|
@@ -413,6 +424,44 @@ class FirmwareManager:
|
|
|
413
424
|
)
|
|
414
425
|
return None, None
|
|
415
426
|
|
|
427
|
+
def _fetch_matching_stable_release_info(
|
|
428
|
+
self, app_feature: tuple[int, int], board: str
|
|
429
|
+
) -> Tuple[str | None, str | None]: # noqa: UP006
|
|
430
|
+
"""The highest non-draft stable release whose feature version is app_feature."""
|
|
431
|
+
line = f"{app_feature[0]}.{app_feature[1]}.x"
|
|
432
|
+
try:
|
|
433
|
+
all_releases = self._fetch_all_releases()
|
|
434
|
+
except requests.RequestException as e:
|
|
435
|
+
logger.error(f"Failed to fetch firmware releases: {e}")
|
|
436
|
+
return None, None
|
|
437
|
+
|
|
438
|
+
candidates = []
|
|
439
|
+
for r in all_releases:
|
|
440
|
+
if r.get("draft"):
|
|
441
|
+
continue
|
|
442
|
+
try:
|
|
443
|
+
tag_version = Version(r.get("tag_name", ""))
|
|
444
|
+
except InvalidVersion:
|
|
445
|
+
continue
|
|
446
|
+
if tag_version.is_prerelease:
|
|
447
|
+
continue
|
|
448
|
+
if (tag_version.major, tag_version.minor) == app_feature:
|
|
449
|
+
candidates.append((tag_version, r))
|
|
450
|
+
|
|
451
|
+
if not candidates:
|
|
452
|
+
logger.error(f"No stable firmware release {line} was found.")
|
|
453
|
+
return None, None
|
|
454
|
+
|
|
455
|
+
_, picked = max(candidates, key=lambda t: t[0])
|
|
456
|
+
download_url = _pick_asset(picked.get("assets", []), board)
|
|
457
|
+
if not download_url:
|
|
458
|
+
logger.error(
|
|
459
|
+
f"Release {picked.get('tag_name')!r} has no asset "
|
|
460
|
+
f"{_asset_label(board)} for board {board!r}."
|
|
461
|
+
)
|
|
462
|
+
return None, None
|
|
463
|
+
return picked.get("tag_name"), download_url
|
|
464
|
+
|
|
416
465
|
def list_releases(
|
|
417
466
|
self,
|
|
418
467
|
channel_filter: Literal["all", "pre", "stable"] = "all",
|
|
@@ -847,8 +896,17 @@ class FirmwareManager:
|
|
|
847
896
|
self._hint_dfu_board()
|
|
848
897
|
return False
|
|
849
898
|
|
|
899
|
+
if app_version is None:
|
|
900
|
+
# Dereferenced at call time, not bound at import, so a test can
|
|
901
|
+
# monkeypatch firestarter.__version__ without reloading this module.
|
|
902
|
+
import firestarter as _pkg
|
|
903
|
+
|
|
904
|
+
app_version = _pkg.__version__
|
|
850
905
|
latest_version, download_url = self.fetch_release_info(
|
|
851
|
-
channel=channel,
|
|
906
|
+
channel=channel,
|
|
907
|
+
version=pinned_version,
|
|
908
|
+
board=board_to_use,
|
|
909
|
+
app_version=app_version,
|
|
852
910
|
)
|
|
853
911
|
|
|
854
912
|
# Guard the release tag where it is BORN, not on each branch that
|
|
@@ -869,12 +927,6 @@ class FirmwareManager:
|
|
|
869
927
|
#
|
|
870
928
|
# The same call refuses a pre-release on a stable CLI. That covers a
|
|
871
929
|
# library caller that asks a stable CLI for channel="pre".
|
|
872
|
-
if app_version is None:
|
|
873
|
-
# Dereferenced at call time, not bound at import, so a test can
|
|
874
|
-
# monkeypatch firestarter.__version__ without reloading this module.
|
|
875
|
-
import firestarter as _pkg
|
|
876
|
-
|
|
877
|
-
app_version = _pkg.__version__
|
|
878
930
|
require_installable_release(
|
|
879
931
|
app_version,
|
|
880
932
|
latest_version,
|
|
@@ -920,8 +972,22 @@ class FirmwareManager:
|
|
|
920
972
|
"only guard."
|
|
921
973
|
)
|
|
922
974
|
|
|
975
|
+
# A stable CLI runs a stable release on its own feature version only. A
|
|
976
|
+
# board on anything else is never up to date, even when its version is
|
|
977
|
+
# higher: a stable 3.2.6 CLI moves a 3.2.3b1 board to 3.2.2.
|
|
978
|
+
mismatched = bool(
|
|
979
|
+
current_version
|
|
980
|
+
and is_prerelease_version(app_version) is False
|
|
981
|
+
and not matches_stable_cli(app_version, current_version)
|
|
982
|
+
)
|
|
923
983
|
is_up_to_date = False
|
|
924
|
-
if
|
|
984
|
+
if mismatched:
|
|
985
|
+
logger.warning(
|
|
986
|
+
f"Firmware {current_version} does not match CLI {app_version}. "
|
|
987
|
+
"A stable CLI uses a stable firmware release with the same "
|
|
988
|
+
"major.minor version."
|
|
989
|
+
)
|
|
990
|
+
elif current_version and latest_version:
|
|
925
991
|
is_up_to_date = self._compare_versions(current_version, latest_version)
|
|
926
992
|
|
|
927
993
|
if is_up_to_date and not force_install:
|
|
@@ -949,10 +1015,12 @@ class FirmwareManager:
|
|
|
949
1015
|
elif (
|
|
950
1016
|
not is_up_to_date and current_version and latest_version
|
|
951
1017
|
): # No flags, but update available
|
|
952
|
-
|
|
953
|
-
f"
|
|
954
|
-
|
|
955
|
-
|
|
1018
|
+
prompt = (
|
|
1019
|
+
f"Install firmware {latest_version} for {board_to_use} (current: {current_version})?" # noqa: E501
|
|
1020
|
+
if mismatched
|
|
1021
|
+
else f"New firmware {latest_version} available for {board_to_use} (current: {current_version}). Update now?" # noqa: E501
|
|
1022
|
+
)
|
|
1023
|
+
if Confirm.ask(prompt, default=False):
|
|
956
1024
|
should_install_now = True
|
|
957
1025
|
else:
|
|
958
1026
|
logger.info("Firmware update cancelled by user.")
|
|
@@ -21,6 +21,11 @@ PEP 440 release; a pre-release firmware is a b/rc/dev tag. This rule ignores the
|
|
|
21
21
|
feature version, and nothing waives it: a stable user gets stable firmware only.
|
|
22
22
|
A pre-release CLI (a b/rc tag, or a dev checkout) installs either.
|
|
23
23
|
|
|
24
|
+
A stable CLI also refuses a release BELOW its feature version. A stable CLI
|
|
25
|
+
3.2.x runs stable firmware 3.2.y only (`matches_stable_cli`). Nothing waives
|
|
26
|
+
this refusal either. `firmware.fetch_release_info` picks the highest matching
|
|
27
|
+
stable release for a stable CLI, so `fw --install` installs a matching release.
|
|
28
|
+
|
|
24
29
|
The reverse pairing -- a CLI newer than the firmware -- is NOT gated here.
|
|
25
30
|
`_maybe_auto_route_to_pre` in cli_handlers.py steers channel selection away from
|
|
26
31
|
it. The two checks cover opposite directions and neither replaces the other.
|
|
@@ -37,7 +42,7 @@ the failed fetch. Turning a GitHub outage into a version-mismatch refusal would
|
|
|
37
42
|
be a lie. This is the one place the gate looks fail-open and is not.
|
|
38
43
|
|
|
39
44
|
Escape for the feature-version refusal: `--allow-newer-firmware`. Never an
|
|
40
|
-
environment variable. The pre-release
|
|
45
|
+
environment variable. The pre-release and older-line refusals have no escape.
|
|
41
46
|
|
|
42
47
|
Unlike the pre-serial gates, the authoritative call runs AFTER the release
|
|
43
48
|
metadata fetch, because the release tag is only known then. It still runs before
|
|
@@ -90,6 +95,14 @@ _PRERELEASE_ON_STABLE_FORMAT = (
|
|
|
90
95
|
"Run 'pip install --upgrade --pre firestarter'."
|
|
91
96
|
)
|
|
92
97
|
|
|
98
|
+
_OLDER_LINE_ON_STABLE_FORMAT = (
|
|
99
|
+
"Firmware {release} is feature version {release_feature}. This CLI is {app}, "
|
|
100
|
+
"a stable release. A stable CLI installs stable firmware {app_feature}.x only. "
|
|
101
|
+
"Refusing to install firmware {release}.\n"
|
|
102
|
+
"Run 'firestarter fw --install' to install the latest stable firmware "
|
|
103
|
+
"{app_feature}.x."
|
|
104
|
+
)
|
|
105
|
+
|
|
93
106
|
_UNREADABLE_TEXT = "an unreadable version"
|
|
94
107
|
|
|
95
108
|
|
|
@@ -123,6 +136,17 @@ def is_prerelease_version(raw: str | None) -> bool | None:
|
|
|
123
136
|
return None
|
|
124
137
|
|
|
125
138
|
|
|
139
|
+
def matches_stable_cli(app_version: str | None, fw_version: str | None) -> bool:
|
|
140
|
+
"""True when fw_version is a stable release on app_version's feature version.
|
|
141
|
+
|
|
142
|
+
The firmware a stable CLI runs. False when either version cannot be read.
|
|
143
|
+
"""
|
|
144
|
+
if is_prerelease_version(fw_version) is not False:
|
|
145
|
+
return False
|
|
146
|
+
app = feature_version(app_version)
|
|
147
|
+
return app is not None and feature_version(fw_version) == app
|
|
148
|
+
|
|
149
|
+
|
|
126
150
|
def is_refused(app_version: str | None, release_version: str | None) -> bool:
|
|
127
151
|
"""True when this CLI must not flash this release.
|
|
128
152
|
|
|
@@ -148,20 +172,30 @@ def require_installable_release(
|
|
|
148
172
|
|
|
149
173
|
Returns None on pass. `allow_newer` waives the feature-version refusal; it
|
|
150
174
|
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
|
|
175
|
+
not get it. Nothing waives the pre-release or older-line refusal on a
|
|
176
|
+
stable CLI.
|
|
152
177
|
|
|
153
|
-
An unreadable version on either side is not judged by the
|
|
178
|
+
An unreadable version on either side is not judged by the stable-CLI rules.
|
|
154
179
|
The feature-version check refuses it.
|
|
155
180
|
"""
|
|
156
|
-
if (
|
|
157
|
-
is_prerelease_version(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
181
|
+
if is_prerelease_version(app_version) is False:
|
|
182
|
+
if is_prerelease_version(release_version) is True:
|
|
183
|
+
raise FirmwareReleaseRefusedError(
|
|
184
|
+
_PRERELEASE_ON_STABLE_FORMAT.format(
|
|
185
|
+
app=app_version, release=release_version
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
app = feature_version(app_version)
|
|
189
|
+
release = feature_version(release_version)
|
|
190
|
+
if app and release and release < app:
|
|
191
|
+
raise FirmwareReleaseRefusedError(
|
|
192
|
+
_OLDER_LINE_ON_STABLE_FORMAT.format(
|
|
193
|
+
app=app_version,
|
|
194
|
+
release=release_version,
|
|
195
|
+
app_feature=f"{app[0]}.{app[1]}",
|
|
196
|
+
release_feature=f"{release[0]}.{release[1]}",
|
|
197
|
+
)
|
|
163
198
|
)
|
|
164
|
-
)
|
|
165
199
|
if allow_newer or not is_refused(app_version, release_version):
|
|
166
200
|
return
|
|
167
201
|
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.7b3
|
|
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
|
|
@@ -17,14 +17,17 @@ Phase 44 Plan 03 additions (read_timing block):
|
|
|
17
17
|
test_consistency_check_default_params_absent_from_command — no extra keys when 0
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
import contextlib
|
|
20
21
|
import logging
|
|
21
22
|
import tempfile
|
|
22
23
|
from pathlib import Path
|
|
23
|
-
from unittest.mock import patch
|
|
24
|
+
from unittest.mock import MagicMock, patch
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
24
27
|
|
|
25
28
|
from firestarter.compare import CompareAccumulator
|
|
26
29
|
from firestarter.config import ConfigManager
|
|
27
|
-
from firestarter.eprom_operations import EpromOperator,
|
|
30
|
+
from firestarter.eprom_operations import EpromOperator, _open_default_read_file
|
|
28
31
|
from firestarter.messages import (
|
|
29
32
|
MSG_DATA_CHUNK,
|
|
30
33
|
MSG_END_DONE,
|
|
@@ -2731,14 +2734,64 @@ def test_seen_message_ids_records_decoded_ids_and_stays_bounded(
|
|
|
2731
2734
|
assert all(isinstance(i, int) for i in comm.seen_message_ids)
|
|
2732
2735
|
|
|
2733
2736
|
|
|
2734
|
-
def
|
|
2737
|
+
def test_default_read_file_takes_lowest_free_suffix(tmp_path, monkeypatch):
|
|
2735
2738
|
"""`read` with no file name never overwrites an earlier read: the
|
|
2736
|
-
default `<NAME>.bin` gets `-1`, `-2`, ... when that name is taken.
|
|
2739
|
+
default `<NAME>.bin` gets `-1`, `-2`, ... when that name is taken.
|
|
2740
|
+
Opening the file claims the name, so the next call skips it."""
|
|
2737
2741
|
monkeypatch.chdir(tmp_path)
|
|
2738
|
-
|
|
2742
|
+
names = []
|
|
2743
|
+
for _ in range(3):
|
|
2744
|
+
path, fh = _open_default_read_file("w27c512")
|
|
2745
|
+
fh.close()
|
|
2746
|
+
names.append(path)
|
|
2747
|
+
assert names == ["W27C512.bin", "W27C512-1.bin", "W27C512-2.bin"]
|
|
2748
|
+
|
|
2749
|
+
|
|
2750
|
+
def _operator_reading(monkeypatch, outcome):
|
|
2751
|
+
"""An EpromOperator whose read writes 4 bytes, then returns or raises `outcome`."""
|
|
2752
|
+
operator = EpromOperator(MagicMock())
|
|
2753
|
+
|
|
2754
|
+
@contextlib.contextmanager
|
|
2755
|
+
def _context(*_args, **_kwargs):
|
|
2756
|
+
yield {"address": 0, "memory-size": 4}, 512, "Read"
|
|
2757
|
+
|
|
2758
|
+
def _state_machine(_op_name, process_data_chunk_callback, **_kwargs):
|
|
2759
|
+
process_data_chunk_callback(0, b"\x01\x02\x03\x04")
|
|
2760
|
+
if isinstance(outcome, BaseException):
|
|
2761
|
+
raise outcome
|
|
2762
|
+
return outcome, None
|
|
2763
|
+
|
|
2764
|
+
monkeypatch.setattr(operator, "_operation_context", _context)
|
|
2765
|
+
monkeypatch.setattr(operator, "_run_state_machine", _state_machine)
|
|
2766
|
+
return operator
|
|
2739
2767
|
|
|
2740
|
-
(tmp_path / "W27C512.bin").write_bytes(b"")
|
|
2741
|
-
assert _default_read_path("w27c512") == "W27C512-1.bin"
|
|
2742
2768
|
|
|
2743
|
-
|
|
2744
|
-
|
|
2769
|
+
def test_read_with_default_name_keeps_the_file(tmp_path, monkeypatch):
|
|
2770
|
+
monkeypatch.chdir(tmp_path)
|
|
2771
|
+
assert _operator_reading(monkeypatch, True).read_eprom("w27c512", {})
|
|
2772
|
+
assert (tmp_path / "W27C512.bin").read_bytes() == b"\x01\x02\x03\x04"
|
|
2773
|
+
|
|
2774
|
+
|
|
2775
|
+
@pytest.mark.parametrize("outcome", [False, KeyboardInterrupt()])
|
|
2776
|
+
def test_failed_read_with_default_name_leaves_no_file(tmp_path, monkeypatch, outcome):
|
|
2777
|
+
"""A failed or interrupted read frees its name, so the next read takes it."""
|
|
2778
|
+
monkeypatch.chdir(tmp_path)
|
|
2779
|
+
(tmp_path / "W27C512.bin").write_bytes(b"earlier read")
|
|
2780
|
+
operator = _operator_reading(monkeypatch, outcome)
|
|
2781
|
+
if isinstance(outcome, BaseException):
|
|
2782
|
+
with pytest.raises(KeyboardInterrupt):
|
|
2783
|
+
operator.read_eprom("w27c512", {})
|
|
2784
|
+
else:
|
|
2785
|
+
assert operator.read_eprom("w27c512", {}) is False
|
|
2786
|
+
assert sorted(p.name for p in tmp_path.iterdir()) == ["W27C512.bin"]
|
|
2787
|
+
assert (tmp_path / "W27C512.bin").read_bytes() == b"earlier read"
|
|
2788
|
+
|
|
2789
|
+
|
|
2790
|
+
def test_failed_read_with_named_file_keeps_it(tmp_path, monkeypatch):
|
|
2791
|
+
"""A file the user named is not removed: it is theirs."""
|
|
2792
|
+
out = tmp_path / "mine.bin"
|
|
2793
|
+
assert (
|
|
2794
|
+
_operator_reading(monkeypatch, False).read_eprom("w27c512", {}, str(out))
|
|
2795
|
+
is False
|
|
2796
|
+
)
|
|
2797
|
+
assert out.exists()
|
|
@@ -1425,12 +1425,12 @@ class TestManageFirmwareUpdate:
|
|
|
1425
1425
|
monkeypatch.setattr(
|
|
1426
1426
|
fm,
|
|
1427
1427
|
"fetch_release_info",
|
|
1428
|
-
lambda channel="stable", version=None, board="uno": (
|
|
1428
|
+
lambda channel="stable", version=None, board="uno", **_kw: (
|
|
1429
1429
|
"3.1.0",
|
|
1430
1430
|
"https://example.com/firestarter_uno.hex",
|
|
1431
1431
|
),
|
|
1432
1432
|
)
|
|
1433
|
-
result = fm.manage_firmware_update(install_flag=True)
|
|
1433
|
+
result = fm.manage_firmware_update(install_flag=True, app_version="3.1.0")
|
|
1434
1434
|
assert result is True
|
|
1435
1435
|
|
|
1436
1436
|
def test_manage_no_current_no_install_intent_returns_false(self, monkeypatch):
|
|
@@ -1442,13 +1442,15 @@ class TestManageFirmwareUpdate:
|
|
|
1442
1442
|
monkeypatch.setattr(
|
|
1443
1443
|
fm,
|
|
1444
1444
|
"fetch_release_info",
|
|
1445
|
-
lambda channel="stable", version=None, board="uno": (
|
|
1445
|
+
lambda channel="stable", version=None, board="uno", **_kw: (
|
|
1446
1446
|
"3.1.0",
|
|
1447
1447
|
"https://example.com/firestarter_uno.hex",
|
|
1448
1448
|
),
|
|
1449
1449
|
)
|
|
1450
1450
|
# install_flag=False + no FLAG_FORCE in flags=0 → returns False
|
|
1451
|
-
result = fm.manage_firmware_update(
|
|
1451
|
+
result = fm.manage_firmware_update(
|
|
1452
|
+
install_flag=False, flags=0, app_version="3.1.0"
|
|
1453
|
+
)
|
|
1452
1454
|
assert result is False
|
|
1453
1455
|
|
|
1454
1456
|
|
|
@@ -1474,7 +1476,7 @@ class TestNewerFirmwareRefusal:
|
|
|
1474
1476
|
monkeypatch.setattr(
|
|
1475
1477
|
fm,
|
|
1476
1478
|
"fetch_release_info",
|
|
1477
|
-
lambda channel="stable", version=None, board="uno": (
|
|
1479
|
+
lambda channel="stable", version=None, board="uno", **_kw: (
|
|
1478
1480
|
latest,
|
|
1479
1481
|
"https://example.com/firestarter_uno.hex" if latest else None,
|
|
1480
1482
|
),
|
{firestarter-3.2.7b2 → firestarter-3.2.7b3}/tests/test_fw_port_targeting_and_blind_install.py
RENAMED
|
@@ -220,7 +220,7 @@ def _manager_that_cannot_identify(monkeypatch):
|
|
|
220
220
|
monkeypatch.setattr(
|
|
221
221
|
fm,
|
|
222
222
|
"fetch_release_info",
|
|
223
|
-
lambda channel="stable", version=None, board="uno": (
|
|
223
|
+
lambda channel="stable", version=None, board="uno", **_kw: (
|
|
224
224
|
"3.0.0b19",
|
|
225
225
|
f"https://example.invalid/{board}.hex",
|
|
226
226
|
),
|
|
@@ -318,7 +318,7 @@ class TestBlindInstallRequiresExplicitBoard:
|
|
|
318
318
|
monkeypatch.setattr(
|
|
319
319
|
fm,
|
|
320
320
|
"fetch_release_info",
|
|
321
|
-
lambda channel="stable", version=None, board="uno": (
|
|
321
|
+
lambda channel="stable", version=None, board="uno", **_kw: (
|
|
322
322
|
"3.0.0b19",
|
|
323
323
|
f"https://example.invalid/{board}.hex",
|
|
324
324
|
),
|