firestarter 3.2.0__tar.gz → 3.2.1__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.
Files changed (219) hide show
  1. {firestarter-3.2.0/firestarter.egg-info → firestarter-3.2.1}/PKG-INFO +2 -1
  2. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/_version.py +3 -3
  3. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/cli_handlers.py +4 -4
  4. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/diagnostic_report.py +4 -4
  5. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/eprom_operations.py +6 -6
  6. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/firmware.py +3 -2
  7. firestarter-3.2.1/firestarter/timestamps.py +83 -0
  8. {firestarter-3.2.0 → firestarter-3.2.1/firestarter.egg-info}/PKG-INFO +2 -1
  9. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/SOURCES.txt +3 -9
  10. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/requires.txt +1 -0
  11. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/scm_file_list.json +3 -9
  12. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/scm_version.json +2 -2
  13. {firestarter-3.2.0 → firestarter-3.2.1}/pyproject.toml +1 -0
  14. {firestarter-3.2.0 → firestarter-3.2.1}/tests/conftest.py +35 -0
  15. firestarter-3.2.0/tests/golden/wire_dict_baseline.json → firestarter-3.2.1/tests/golden/wire_dict.json +189 -137
  16. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_derive_plan_no_drop_sweep.py +14 -22
  17. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_devtest_firmware_error_propagation.py +11 -4
  18. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_erase_flag_invariants.py +20 -30
  19. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_firmware_install.py +4 -2
  20. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_hardware.py +10 -2
  21. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_page_size_invariants.py +22 -44
  22. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_probe_spurious_setup_ack.py +3 -0
  23. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protocol_not_implemented_production_path.py +4 -1
  24. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_sdp_db_invariant.py +22 -62
  25. firestarter-3.2.1/tests/test_timestamps.py +87 -0
  26. firestarter-3.2.1/tests/test_wire_dict_equivalence.py +177 -0
  27. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_149.json +0 -65
  28. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_153.json +0 -265
  29. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_182.json +0 -219
  30. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_194.json +0 -86
  31. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_197.json +0 -20
  32. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_198.json +0 -17
  33. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_199.json +0 -14
  34. firestarter-3.2.0/tests/golden/wire_dict_expected_deltas_dip24_2816.json +0 -91
  35. firestarter-3.2.0/tests/test_wire_dict_equivalence.py +0 -821
  36. {firestarter-3.2.0 → firestarter-3.2.1}/LICENSE +0 -0
  37. {firestarter-3.2.0 → firestarter-3.2.1}/MANIFEST.in +0 -0
  38. {firestarter-3.2.0 → firestarter-3.2.1}/README.md +0 -0
  39. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/__init__.py +0 -0
  40. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/address_parser.py +0 -0
  41. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/avr_tool.py +0 -0
  42. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/channel.py +0 -0
  43. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/chip_resolver.py +0 -0
  44. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/chip_test.py +0 -0
  45. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/codec.py +0 -0
  46. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/compare.py +0 -0
  47. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/config.py +0 -0
  48. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/constants.py +0 -0
  49. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/data/chip_database.json +0 -0
  50. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/data/pinouts.json +0 -0
  51. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/database.py +0 -0
  52. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/dip24_2816_erase_gate.py +0 -0
  53. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/eprom_info.py +0 -0
  54. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/erase_support.py +0 -0
  55. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/exceptions.py +0 -0
  56. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/flash4_erase_gate.py +0 -0
  57. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/frame_parser.py +0 -0
  58. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/fw_release_gate.py +0 -0
  59. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/hardware.py +0 -0
  60. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/ic_layout.py +0 -0
  61. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/jp5_gate.py +0 -0
  62. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/jumper_table.py +0 -0
  63. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/lock_status.py +0 -0
  64. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/log_capture.py +0 -0
  65. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/logging_utils.py +0 -0
  66. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/main.py +0 -0
  67. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/messages.py +0 -0
  68. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/page_size_gate.py +0 -0
  69. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/protection_readability.py +0 -0
  70. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/py32_dfu.py +0 -0
  71. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/sdp_capability.py +0 -0
  72. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/sdp_honesty.py +0 -0
  73. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/serial_comm.py +0 -0
  74. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/submit.py +0 -0
  75. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/transport_counters.py +0 -0
  76. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/utils.py +0 -0
  77. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/vpp_display.py +0 -0
  78. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter/write_blank_guard.py +0 -0
  79. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/dependency_links.txt +0 -0
  80. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/entry_points.txt +0 -0
  81. {firestarter-3.2.0 → firestarter-3.2.1}/firestarter.egg-info/top_level.txt +0 -0
  82. {firestarter-3.2.0 → firestarter-3.2.1}/setup.cfg +0 -0
  83. {firestarter-3.2.0 → firestarter-3.2.1}/tests/__init__.py +0 -0
  84. {firestarter-3.2.0 → firestarter-3.2.1}/tests/__snapshots__/test_characterization.ambr +0 -0
  85. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fake_chip.py +0 -0
  86. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/part_number_delta.json +0 -0
  87. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/report_shapes.py +0 -0
  88. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/at28c256-full-all-ok-sdp.json +0 -0
  89. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/attr01-status-axis-transport-fault.json +0 -0
  90. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/gh20-at28c256-fail.json +0 -0
  91. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/gh23-w27e257-fail.json +0 -0
  92. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/gh28-m27c512-fail.json +0 -0
  93. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/gh47-sst27sf512-pass.json +0 -0
  94. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/m27c512-full-all-ok.json +0 -0
  95. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/m27c512-full-blank-check-bad.json +0 -0
  96. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/m27c512-full-canonical-name.json +0 -0
  97. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/m27c512-full-comma-joined-name.json +0 -0
  98. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/m27c512-full-runs-1.json +0 -0
  99. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/prune03-synthesized-fingerprint-match.json +0 -0
  100. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/sst27sf512-full-all-ok.json +0 -0
  101. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/sst27sf512-six-step-readback-gated.json +0 -0
  102. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/sst27sf512-six-step.json +0 -0
  103. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/synthetic-arm4-empty-results.json +0 -0
  104. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/synthetic-arm4-no-ok.json +0 -0
  105. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/uv-slot-write-pass.json +0 -0
  106. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/reports/w27e257-full-all-ok.json +0 -0
  107. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/shape_ids.json +0 -0
  108. {firestarter-3.2.0 → firestarter-3.2.1}/tests/fixtures/synthetic_nonzero_chip_id.py +0 -0
  109. {firestarter-3.2.0 → firestarter-3.2.1}/tests/golden/build_db_part_specific_constants.json +0 -0
  110. {firestarter-3.2.0 → firestarter-3.2.1}/tests/golden/chip_database_field_inventory.json +0 -0
  111. {firestarter-3.2.0 → firestarter-3.2.1}/tests/plan_corpus.py +0 -0
  112. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_address_parser.py +0 -0
  113. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_b15_page_size_corroboration.py +0 -0
  114. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_blast_radius_invariance.py +0 -0
  115. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_boot_block_hint.py +0 -0
  116. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_budget_failure_render.py +0 -0
  117. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_bug_characterization.py +0 -0
  118. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_build_db_constant_census.py +0 -0
  119. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_build_db_inclusion.py +0 -0
  120. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_build_db_interpret_timing.py +0 -0
  121. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_build_db_pinout_fork.py +0 -0
  122. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_canonical_part_number.py +0 -0
  123. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_characterization.py +0 -0
  124. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_database_field_inventory.py +0 -0
  125. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_resolver.py +0 -0
  126. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test.py +0 -0
  127. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test_blank_check_order.py +0 -0
  128. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test_cycle.py +0 -0
  129. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test_sdp_leg.py +0 -0
  130. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test_timing.py +0 -0
  131. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_chip_test_uv_slot_write.py +0 -0
  132. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_cli_handlers.py +0 -0
  133. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_click_group_gate_hook.py +0 -0
  134. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_cobs.py +0 -0
  135. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_codec.py +0 -0
  136. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_codec_format_message.py +0 -0
  137. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_compare.py +0 -0
  138. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_config.py +0 -0
  139. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_config_band_refusal.py +0 -0
  140. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_connect_cost_harness.py +0 -0
  141. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_consistency_check.py +0 -0
  142. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_coverage_floor_v18.py +0 -0
  143. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_database_conversion.py +0 -0
  144. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_datasheet_overrides.py +0 -0
  145. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_decoder.py +0 -0
  146. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_derive_plan_structural_sentinel.py +0 -0
  147. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_dev_group_channel_gating.py +0 -0
  148. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_dev_test_cmd.py +0 -0
  149. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_dev_tools_channel_gate.py +0 -0
  150. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_dfu_opcode_anchors.py +0 -0
  151. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_diagnostic_report.py +0 -0
  152. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_dip24_2816_erase_gate.py +0 -0
  153. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_endpoint_constants.py +0 -0
  154. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_eprom_database.py +0 -0
  155. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_eprom_info.py +0 -0
  156. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_eprom_operations.py +0 -0
  157. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_erase_blank_step_nonregression.py +0 -0
  158. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_error_code_seam.py +0 -0
  159. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_even_block.py +0 -0
  160. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_extra_chips_supplement.py +0 -0
  161. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_flash4_erase_gate.py +0 -0
  162. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_list_failure_vs_empty.py +0 -0
  163. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_port_targeting_and_blind_install.py +0 -0
  164. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_release_gate.py +0 -0
  165. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_update_dead_endpoint.py +0 -0
  166. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_update_path_gate.py +0 -0
  167. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fw_version_guard.py +0 -0
  168. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_fwguard.py +0 -0
  169. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_hw_revision_gate.py +0 -0
  170. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_ic_layout.py +0 -0
  171. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_info_vpp_and_erase.py +0 -0
  172. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_jp5_gate.py +0 -0
  173. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_jumper_table.py +0 -0
  174. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_lock_status_class_partition.py +0 -0
  175. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_lock_status_cli.py +0 -0
  176. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_lock_status_wire.py +0 -0
  177. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_log_capture.py +0 -0
  178. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_logging_utils.py +0 -0
  179. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_matrix_artifact.py +0 -0
  180. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_page_size_alignment_refusal.py +0 -0
  181. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_page_size_write_refusal.py +0 -0
  182. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_parse_devtest_issue.py +0 -0
  183. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_programming_vcc_census.py +0 -0
  184. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protect_flags_doc_measurements.py +0 -0
  185. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protection_resolution.py +0 -0
  186. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protection_status_catalog.py +0 -0
  187. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protection_table_citations.py +0 -0
  188. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_protocol_not_implemented.py +0 -0
  189. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_provenance.py +0 -0
  190. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_pulse_us_override.py +0 -0
  191. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_py32_channel_gating.py +0 -0
  192. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_py32_dfu.py +0 -0
  193. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_py32_flash_map_host.py +0 -0
  194. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_py32_pyusb_absent.py +0 -0
  195. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_pyusb_api_surface.py +0 -0
  196. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_sdp_capability.py +0 -0
  197. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_sdp_honesty.py +0 -0
  198. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_serial_characterization.py +0 -0
  199. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_serial_comm.py +0 -0
  200. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_session_lease.py +0 -0
  201. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_setup_refusals.py +0 -0
  202. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_submit.py +0 -0
  203. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_transport_counters.py +0 -0
  204. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_utils.py +0 -0
  205. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_uv_mask.py +0 -0
  206. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_validate_family_cmd.py +0 -0
  207. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_validate_oracle.py +0 -0
  208. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_validated_parts_regression_surface.py +0 -0
  209. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_vcc_margin_rail.py +0 -0
  210. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_vpp_decode_table.py +0 -0
  211. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_vpp_rail_classification.py +0 -0
  212. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_blank_guard.py +0 -0
  213. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_blank_guard_pinning.py +0 -0
  214. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_guard_port_pinning.py +0 -0
  215. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_progress.py +0 -0
  216. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_response_budget.py +0 -0
  217. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_skip_erase_0x0d.py +0 -0
  218. {firestarter-3.2.0 → firestarter-3.2.1}/tests/test_write_skip_sdp_unlock.py +0 -0
  219. {firestarter-3.2.0 → firestarter-3.2.1}/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.0
3
+ Version: 3.2.1
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
@@ -37,6 +37,7 @@ Requires-Dist: syrupy<7,>=5.0; extra == "test"
37
37
  Requires-Dist: ruff>=0.15.14; extra == "test"
38
38
  Requires-Dist: mypy<3,>=2.1.0; extra == "test"
39
39
  Requires-Dist: pytest-cov>=7.1.0; extra == "test"
40
+ Requires-Dist: pytest-xdist>=3.6; extra == "test"
40
41
  Requires-Dist: types-pyserial>=3.5.0.20260519; extra == "test"
41
42
  Dynamic: license-file
42
43
 
@@ -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.0'
22
- __version_tuple__ = version_tuple = (3, 2, 0)
21
+ __version__ = version = '3.2.1'
22
+ __version_tuple__ = version_tuple = (3, 2, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g42fa55c'
24
+ __commit_id__ = commit_id = 'g4526a89'
@@ -4,7 +4,6 @@ This module is the production CLI surface; main.py re-exports `cli` as `main`
4
4
  for the console-script entry point.
5
5
  """
6
6
 
7
- import datetime
8
7
  import functools
9
8
  import hashlib
10
9
  import json
@@ -106,6 +105,7 @@ from firestarter.protection_readability import (
106
105
  protection_gate_for_entry,
107
106
  )
108
107
  from firestarter.sdp_capability import SDP_PROTOCOL_ID, sdp_capability
108
+ from firestarter.timestamps import iso_cet
109
109
 
110
110
  logger = logging.getLogger("Firestarter")
111
111
 
@@ -1908,10 +1908,10 @@ def fw(
1908
1908
 
1909
1909
  print(_json.dumps(releases, indent=2))
1910
1910
  else:
1911
- print(f"{'Version':<12} {'Channel':<14} {'Published':<22} Asset URL")
1911
+ print(f"{'Version':<12} {'Channel':<14} {'Published':<25} Asset URL")
1912
1912
  for r in releases:
1913
1913
  print(
1914
- f"{r['version']:<12} {r['channel']:<14} {r['published']:<22} {r['asset_url']}" # noqa: E501
1914
+ f"{r['version']:<12} {r['channel']:<14} {r['published']:<25} {r['asset_url']}" # noqa: E501
1915
1915
  )
1916
1916
  if not releases:
1917
1917
  print(f"No releases found for board {board}.")
@@ -2638,7 +2638,7 @@ def _write_artifact(
2638
2638
  out_path.mkdir(parents=True, exist_ok=True)
2639
2639
 
2640
2640
  artifact: Dict[str, Any] = { # noqa: UP006
2641
- "generated": datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
2641
+ "generated": iso_cet(),
2642
2642
  "harness_version": "71",
2643
2643
  "cells": cells,
2644
2644
  }
@@ -23,7 +23,6 @@ human-input field gates it.
23
23
 
24
24
  from __future__ import annotations
25
25
 
26
- import datetime
27
26
  import hashlib
28
27
  import json
29
28
  from dataclasses import dataclass, field
@@ -45,6 +44,7 @@ from firestarter.chip_test import (
45
44
  repeat_policy_tag,
46
45
  resolve_error_name,
47
46
  )
47
+ from firestarter.timestamps import iso_cet
48
48
 
49
49
  # ---------------------------------------------------------------------------
50
50
  # Module constants -- single sources of truth
@@ -774,8 +774,8 @@ class DiagnosticReport:
774
774
  over dataclass fields, so this field's absence from that list is the
775
775
  whole exclusion mechanism."""
776
776
 
777
- def _utc_now(self) -> str:
778
- return datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
777
+ def _now(self) -> str:
778
+ return iso_cet()
779
779
 
780
780
  def _auto_capture_dict(self) -> dict[str, Any]:
781
781
  ac = self.auto_capture
@@ -1065,7 +1065,7 @@ class DiagnosticReport:
1065
1065
  """
1066
1066
  return {
1067
1067
  "schema_version": SCHEMA_VERSION,
1068
- "generated": self._utc_now(),
1068
+ "generated": self._now(),
1069
1069
  "elapsed": self.elapsed,
1070
1070
  "auto_capture": self._auto_capture_dict(),
1071
1071
  "transport_health": self._transport_dict(),
@@ -15,7 +15,6 @@ import shutil
15
15
  import statistics
16
16
  import time
17
17
  from contextlib import contextmanager
18
- from datetime import datetime
19
18
  from pathlib import Path
20
19
  from typing import Callable, Dict, Tuple # noqa: UP035
21
20
 
@@ -75,6 +74,7 @@ from firestarter.serial_comm import (
75
74
  WRITE_BUDGET_MAX_S,
76
75
  SerialCommunicator,
77
76
  )
77
+ from firestarter.timestamps import iso_cet_basic
78
78
  from firestarter.utils import extract_hex_to_decimal
79
79
  from firestarter.write_blank_guard import (
80
80
  incomplete_refusal_text,
@@ -1383,7 +1383,7 @@ class EpromOperator:
1383
1383
  # path is to use the actual board name -- but the unit-test
1384
1384
  # surface doesn't have a serial connection, so we fall back).
1385
1385
  if output_dir is None:
1386
- timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
1386
+ timestamp = iso_cet_basic()
1387
1387
  output_dir = str(
1388
1388
  Path(DEFAULT_RUN_OUTPUT_DIR)
1389
1389
  / f"consistency-check-{eprom_name}-unknown-board-{timestamp}"
@@ -1560,7 +1560,7 @@ class EpromOperator:
1560
1560
  source_sha = hashlib.sha256(Path(source_image_path).read_bytes()).hexdigest()
1561
1561
 
1562
1562
  if output_dir is None:
1563
- timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
1563
+ timestamp = iso_cet_basic()
1564
1564
  output_dir = str(
1565
1565
  Path(DEFAULT_RUN_OUTPUT_DIR)
1566
1566
  / f"write-cycle-{eprom_name}-unknown-board-{timestamp}"
@@ -1678,7 +1678,7 @@ class EpromOperator:
1678
1678
  from firestarter.serial_comm import FaultInjectingSerialCommunicator
1679
1679
 
1680
1680
  if output_dir is None:
1681
- timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
1681
+ timestamp = iso_cet_basic()
1682
1682
  output_dir = f"fault-inject-{eprom_name}-{direction}-{timestamp}"
1683
1683
  output_path = Path(output_dir)
1684
1684
  output_path.mkdir(parents=True, exist_ok=True)
@@ -1945,7 +1945,7 @@ class EpromOperator:
1945
1945
  return False
1946
1946
 
1947
1947
  if output_dir is None:
1948
- timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
1948
+ timestamp = iso_cet_basic()
1949
1949
  output_dir = f"fault-inject-latency-{fault_form}-{timestamp}"
1950
1950
  output_path = Path(output_dir)
1951
1951
  output_path.mkdir(parents=True, exist_ok=True)
@@ -2149,7 +2149,7 @@ class EpromOperator:
2149
2149
  return False
2150
2150
 
2151
2151
  if output_dir is None:
2152
- timestamp = datetime.now().strftime("%Y-%m-%d-%H%M%S")
2152
+ timestamp = iso_cet_basic()
2153
2153
  output_dir = f"connect-cost-{timestamp}"
2154
2154
  output_path = Path(output_dir)
2155
2155
  output_path.mkdir(parents=True, exist_ok=True)
@@ -41,6 +41,7 @@ from firestarter.exceptions import (
41
41
  )
42
42
  from firestarter.fw_release_gate import require_installable_release
43
43
  from firestarter.serial_comm import SerialCommunicator
44
+ from firestarter.timestamps import iso_text_to_cet
44
45
 
45
46
  logger = logging.getLogger("Firmware")
46
47
 
@@ -62,7 +63,7 @@ class ReleaseInfo(TypedDict):
62
63
  version: str # tag_name from GitHub API
63
64
  tag: str # raw tag_name (same as version for firmware releases)
64
65
  channel: str # "stable" or "prerelease"
65
- published: str # ISO-8601 from published_at field
66
+ published: str # published_at as ISO 8601 in CET
66
67
  asset_url: str # browser_download_url for the board-matching .hex asset
67
68
 
68
69
 
@@ -468,7 +469,7 @@ class FirmwareManager:
468
469
  version=tag,
469
470
  tag=tag,
470
471
  channel="prerelease" if is_pre else "stable",
471
- published=r.get("published_at") or "",
472
+ published=iso_text_to_cet(r.get("published_at") or ""),
472
473
  asset_url=asset_url,
473
474
  )
474
475
  )
@@ -0,0 +1,83 @@
1
+ """
2
+ Project Name: Firestarter
3
+ Copyright (c) 2024 Henrik Olsson
4
+
5
+ Permission is hereby granted under MIT license.
6
+
7
+ Timestamp Formatting Module
8
+
9
+ Every timestamp the host writes is ISO 8601 in Central European Time: CET
10
+ (UTC+01:00) in winter and CEST (UTC+02:00) during EU summer time. Summer time
11
+ starts on the last Sunday of March and ends on the last Sunday of October, at
12
+ 01:00 UTC both times.
13
+
14
+ The EU rule is computed here instead of through `zoneinfo`, because Windows
15
+ and slim Linux images have no tz database and the app does not depend on the
16
+ `tzdata` package.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import datetime
22
+
23
+ _CET = datetime.timezone(datetime.timedelta(hours=1), "CET")
24
+ _CEST = datetime.timezone(datetime.timedelta(hours=2), "CEST")
25
+
26
+
27
+ def _last_sunday_0100_utc(year: int, month: int) -> datetime.datetime:
28
+ """01:00 UTC on the last Sunday of `month` (March or October, both 31 days)."""
29
+ last_day = datetime.datetime(year, month, 31, 1, tzinfo=datetime.UTC)
30
+ return last_day - datetime.timedelta(days=(last_day.weekday() + 1) % 7)
31
+
32
+
33
+ def to_cet(moment: datetime.datetime) -> datetime.datetime:
34
+ """Return `moment` converted to CET or CEST.
35
+
36
+ Raises ValueError for a naive `moment`, because its zone is unknown.
37
+ """
38
+ if moment.tzinfo is None:
39
+ raise ValueError("to_cet() needs a timezone-aware datetime")
40
+ year = moment.astimezone(datetime.UTC).year
41
+ summer_start = _last_sunday_0100_utc(year, 3)
42
+ summer_end = _last_sunday_0100_utc(year, 10)
43
+ return moment.astimezone(_CEST if summer_start <= moment < summer_end else _CET)
44
+
45
+
46
+ def now_cet() -> datetime.datetime:
47
+ """The current time in CET or CEST."""
48
+ return to_cet(datetime.datetime.now(datetime.UTC))
49
+
50
+
51
+ def iso_cet(moment: datetime.datetime | None = None) -> str:
52
+ """ISO 8601 extended format, e.g. `2026-10-09T14:30:56+02:00`.
53
+
54
+ Uses the current time when `moment` is None.
55
+ """
56
+ cet = now_cet() if moment is None else to_cet(moment)
57
+ return cet.isoformat(timespec="seconds")
58
+
59
+
60
+ def iso_cet_basic(moment: datetime.datetime | None = None) -> str:
61
+ """ISO 8601 basic format for file and directory names, e.g.
62
+ `20261009T143056+0200`.
63
+
64
+ The basic format has no colons. Windows does not permit a colon in a path.
65
+ Uses the current time when `moment` is None.
66
+ """
67
+ cet = now_cet() if moment is None else to_cet(moment)
68
+ return cet.strftime("%Y%m%dT%H%M%S%z")
69
+
70
+
71
+ def iso_text_to_cet(text: str) -> str:
72
+ """Return the ISO 8601 timestamp `text` (e.g. GitHub's
73
+ `2026-01-01T00:00:00Z`) in CET, in extended format.
74
+
75
+ Returns `text` unchanged when it does not parse or has no UTC offset.
76
+ """
77
+ try:
78
+ moment = datetime.datetime.fromisoformat(text)
79
+ except (TypeError, ValueError):
80
+ return text
81
+ if moment.tzinfo is None:
82
+ return text
83
+ return iso_cet(moment)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firestarter
3
- Version: 3.2.0
3
+ Version: 3.2.1
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
@@ -37,6 +37,7 @@ Requires-Dist: syrupy<7,>=5.0; extra == "test"
37
37
  Requires-Dist: ruff>=0.15.14; extra == "test"
38
38
  Requires-Dist: mypy<3,>=2.1.0; extra == "test"
39
39
  Requires-Dist: pytest-cov>=7.1.0; extra == "test"
40
+ Requires-Dist: pytest-xdist>=3.6; extra == "test"
40
41
  Requires-Dist: types-pyserial>=3.5.0.20260519; extra == "test"
41
42
  Dynamic: license-file
42
43
 
@@ -41,6 +41,7 @@ firestarter/sdp_capability.py
41
41
  firestarter/sdp_honesty.py
42
42
  firestarter/serial_comm.py
43
43
  firestarter/submit.py
44
+ firestarter/timestamps.py
44
45
  firestarter/transport_counters.py
45
46
  firestarter/utils.py
46
47
  firestarter/vpp_display.py
@@ -159,6 +160,7 @@ tests/test_serial_comm.py
159
160
  tests/test_session_lease.py
160
161
  tests/test_setup_refusals.py
161
162
  tests/test_submit.py
163
+ tests/test_timestamps.py
162
164
  tests/test_transport_counters.py
163
165
  tests/test_utils.py
164
166
  tests/test_uv_mask.py
@@ -203,12 +205,4 @@ tests/fixtures/reports/uv-slot-write-pass.json
203
205
  tests/fixtures/reports/w27e257-full-all-ok.json
204
206
  tests/golden/build_db_part_specific_constants.json
205
207
  tests/golden/chip_database_field_inventory.json
206
- tests/golden/wire_dict_baseline.json
207
- tests/golden/wire_dict_expected_deltas_149.json
208
- tests/golden/wire_dict_expected_deltas_153.json
209
- tests/golden/wire_dict_expected_deltas_182.json
210
- tests/golden/wire_dict_expected_deltas_194.json
211
- tests/golden/wire_dict_expected_deltas_197.json
212
- tests/golden/wire_dict_expected_deltas_198.json
213
- tests/golden/wire_dict_expected_deltas_199.json
214
- tests/golden/wire_dict_expected_deltas_dip24_2816.json
208
+ tests/golden/wire_dict.json
@@ -17,4 +17,5 @@ syrupy<7,>=5.0
17
17
  ruff>=0.15.14
18
18
  mypy<3,>=2.1.0
19
19
  pytest-cov>=7.1.0
20
+ pytest-xdist>=3.6
20
21
  types-pyserial>=3.5.0.20260519
@@ -67,6 +67,7 @@
67
67
  "firestarter/sdp_honesty.py",
68
68
  "firestarter/serial_comm.py",
69
69
  "firestarter/submit.py",
70
+ "firestarter/timestamps.py",
70
71
  "firestarter/transport_counters.py",
71
72
  "firestarter/utils.py",
72
73
  "firestarter/vpp_display.py",
@@ -103,15 +104,7 @@
103
104
  "tests/fixtures/synthetic_nonzero_chip_id.py",
104
105
  "tests/golden/build_db_part_specific_constants.json",
105
106
  "tests/golden/chip_database_field_inventory.json",
106
- "tests/golden/wire_dict_baseline.json",
107
- "tests/golden/wire_dict_expected_deltas_149.json",
108
- "tests/golden/wire_dict_expected_deltas_153.json",
109
- "tests/golden/wire_dict_expected_deltas_182.json",
110
- "tests/golden/wire_dict_expected_deltas_194.json",
111
- "tests/golden/wire_dict_expected_deltas_197.json",
112
- "tests/golden/wire_dict_expected_deltas_198.json",
113
- "tests/golden/wire_dict_expected_deltas_199.json",
114
- "tests/golden/wire_dict_expected_deltas_dip24_2816.json",
107
+ "tests/golden/wire_dict.json",
115
108
  "tests/plan_corpus.py",
116
109
  "tests/test_address_parser.py",
117
110
  "tests/test_b15_page_size_corroboration.py",
@@ -213,6 +206,7 @@
213
206
  "tests/test_session_lease.py",
214
207
  "tests/test_setup_refusals.py",
215
208
  "tests/test_submit.py",
209
+ "tests/test_timestamps.py",
216
210
  "tests/test_transport_counters.py",
217
211
  "tests/test_utils.py",
218
212
  "tests/test_uv_mask.py",
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "3.2.0",
2
+ "tag": "3.2.1",
3
3
  "distance": 0,
4
- "node": "g42fa55c",
4
+ "node": "g4526a89",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-25"
@@ -73,6 +73,7 @@ test = [
73
73
  "ruff>=0.15.14",
74
74
  "mypy>=2.1.0,<3",
75
75
  "pytest-cov>=7.1.0",
76
+ "pytest-xdist>=3.6",
76
77
  "types-pyserial>=3.5.0.20260519",
77
78
  ]
78
79
 
@@ -37,6 +37,7 @@ from __future__ import annotations
37
37
 
38
38
  import importlib.util
39
39
  import io
40
+ import os
40
41
  import struct
41
42
  from typing import TYPE_CHECKING, cast
42
43
  from unittest.mock import Mock
@@ -96,6 +97,40 @@ collect_ignore: list = []
96
97
  if _pyusb_is_absent():
97
98
  collect_ignore.append("test_pyusb_api_surface.py")
98
99
 
100
+
101
+ # `dev test` runs a `gh issue list --author @me` dedup query through
102
+ # `submit.find_prior_report`, bound as a default argument, so patching the
103
+ # module attribute does not reach it. A stub `gh` first on PATH answers every
104
+ # call as "not authenticated" (exit 4): no test waits on GitHub, depends on the
105
+ # developer's gh login, or branches on their real issues.
106
+ @pytest.fixture(autouse=True)
107
+ def _offline_gh(tmp_path_factory, monkeypatch):
108
+ bin_dir = tmp_path_factory.getbasetemp() / "offline-gh-bin"
109
+ gh = bin_dir / "gh"
110
+ if not gh.exists():
111
+ bin_dir.mkdir(exist_ok=True)
112
+ gh.write_text("#!/bin/sh\nexit 4\n")
113
+ gh.chmod(0o755)
114
+ monkeypatch.setenv("PATH", f"{bin_dir}{os.pathsep}{os.environ.get('PATH', '')}")
115
+
116
+
117
+ # `disconnect()` drains the port with `consume_remaining_input(timeout=0.5)`.
118
+ # A fake serial port answers every read at once, so the drain only ends when
119
+ # the window runs out: 0.5 s of wall time per disconnect, a full second for a
120
+ # write with a guard read. Any parsed frame restarts the window, so a short one
121
+ # still drains everything a test fed. An explicit timeout is kept.
122
+ @pytest.fixture(autouse=True)
123
+ def _short_drain_window(monkeypatch):
124
+ from firestarter.serial_comm import SerialCommunicator
125
+
126
+ drain = SerialCommunicator.consume_remaining_input
127
+
128
+ def _short_drain(self, timeout: float = 0.02) -> None:
129
+ drain(self, timeout)
130
+
131
+ monkeypatch.setattr(SerialCommunicator, "consume_remaining_input", _short_drain)
132
+
133
+
99
134
  # Module-level reference constants — independent of firestarter.serial_comm
100
135
  # so tests do not pass tautologically on a bug in the production constant.
101
136
  MAGIC_PREAMBLE_REF: bytes = b"\xaa\x55\xaa\x55"