statable 3.5.2__tar.gz → 3.5.3__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 (177) hide show
  1. {statable-3.5.2 → statable-3.5.3}/PKG-INFO +1 -1
  2. {statable-3.5.2 → statable-3.5.3}/codegen/__init__.py +1 -1
  3. {statable-3.5.2 → statable-3.5.3}/pyproject.toml +1 -1
  4. {statable-3.5.2 → statable-3.5.3}/statable/__init__.py +1 -1
  5. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/PKG-INFO +1 -1
  6. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/SOURCES.txt +1 -0
  7. statable-3.5.3/tests/test_v3_5_3_eclipse_installer_deep.py +337 -0
  8. {statable-3.5.2 → statable-3.5.3}/README.md +0 -0
  9. {statable-3.5.2 → statable-3.5.3}/codegen/c_code_generator.py +0 -0
  10. {statable-3.5.2 → statable-3.5.3}/codegen/code_merger.py +0 -0
  11. {statable-3.5.2 → statable-3.5.3}/codegen/code_templates.py +0 -0
  12. {statable-3.5.2 → statable-3.5.3}/codegen/config.py +0 -0
  13. {statable-3.5.2 → statable-3.5.3}/codegen/enum_generator.py +0 -0
  14. {statable-3.5.2 → statable-3.5.3}/codegen/event_queue_generator.py +0 -0
  15. {statable-3.5.2 → statable-3.5.3}/codegen/interrupt_generator.py +0 -0
  16. {statable-3.5.2 → statable-3.5.3}/codegen/naming_convention.py +0 -0
  17. {statable-3.5.2 → statable-3.5.3}/codegen/osal_generator.py +0 -0
  18. {statable-3.5.2 → statable-3.5.3}/codegen/role_function_generator.py +0 -0
  19. {statable-3.5.2 → statable-3.5.3}/codegen/sample_data.py +0 -0
  20. {statable-3.5.2 → statable-3.5.3}/codegen/state_actions_generator.py +0 -0
  21. {statable-3.5.2 → statable-3.5.3}/codegen/struct_generator.py +0 -0
  22. {statable-3.5.2 → statable-3.5.3}/codegen/timer_generator.py +0 -0
  23. {statable-3.5.2 → statable-3.5.3}/codegen/transition_generator.py +0 -0
  24. {statable-3.5.2 → statable-3.5.3}/codegen/type_mapper.py +0 -0
  25. {statable-3.5.2 → statable-3.5.3}/codegen/validate/__init__.py +0 -0
  26. {statable-3.5.2 → statable-3.5.3}/codegen/validate/change_actions.py +0 -0
  27. {statable-3.5.2 → statable-3.5.3}/codegen/validate/change_applier.py +0 -0
  28. {statable-3.5.2 → statable-3.5.3}/codegen/validate/clipboard_manager.py +0 -0
  29. {statable-3.5.2 → statable-3.5.3}/codegen/validate/data/__init__.py +0 -0
  30. {statable-3.5.2 → statable-3.5.3}/codegen/validate/data/action_definitions.py +0 -0
  31. {statable-3.5.2 → statable-3.5.3}/codegen/validate/data/keywords.py +0 -0
  32. {statable-3.5.2 → statable-3.5.3}/codegen/validate/data/prompt_templates.py +0 -0
  33. {statable-3.5.2 → statable-3.5.3}/codegen/validate/data/validation_rules.py +0 -0
  34. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/__init__.py +0 -0
  35. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/base_validator.py +0 -0
  36. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/cell_validator.py +0 -0
  37. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/custom_type_validator.py +0 -0
  38. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/event_validator.py +0 -0
  39. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/flag_validator.py +0 -0
  40. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/interrupt_validator.py +0 -0
  41. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/queue_validator.py +0 -0
  42. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/role_function_validator.py +0 -0
  43. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/state_validator.py +0 -0
  44. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/timer_validator.py +0 -0
  45. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/transition_validator.py +0 -0
  46. {statable-3.5.2 → statable-3.5.3}/codegen/validate/items/variable_validator.py +0 -0
  47. {statable-3.5.2 → statable-3.5.3}/codegen/validate/logger.py +0 -0
  48. {statable-3.5.2 → statable-3.5.3}/codegen/validate/models.py +0 -0
  49. {statable-3.5.2 → statable-3.5.3}/codegen/validate/prompt_generator.py +0 -0
  50. {statable-3.5.2 → statable-3.5.3}/codegen/validate/response_parser.py +0 -0
  51. {statable-3.5.2 → statable-3.5.3}/codegen/validate/response_validator.py +0 -0
  52. {statable-3.5.2 → statable-3.5.3}/codegen/validate/validation_dialog.py +0 -0
  53. {statable-3.5.2 → statable-3.5.3}/codegen/validate/validator.py +0 -0
  54. {statable-3.5.2 → statable-3.5.3}/codegen/variable_generator.py +0 -0
  55. {statable-3.5.2 → statable-3.5.3}/setup.cfg +0 -0
  56. {statable-3.5.2 → statable-3.5.3}/statable/__main__.py +0 -0
  57. {statable-3.5.2 → statable-3.5.3}/statable/cli.py +0 -0
  58. {statable-3.5.2 → statable-3.5.3}/statable/global_defs.py +0 -0
  59. {statable-3.5.2 → statable-3.5.3}/statable/mermaid_gen.py +0 -0
  60. {statable-3.5.2 → statable-3.5.3}/statable/model.py +0 -0
  61. {statable-3.5.2 → statable-3.5.3}/statable/output_utils.py +0 -0
  62. {statable-3.5.2 → statable-3.5.3}/statable/parser.py +0 -0
  63. {statable-3.5.2 → statable-3.5.3}/statable/sample_data.py +0 -0
  64. {statable-3.5.2 → statable-3.5.3}/statable/shared/__init__.py +0 -0
  65. {statable-3.5.2 → statable-3.5.3}/statable/shared/condition_library.py +0 -0
  66. {statable-3.5.2 → statable-3.5.3}/statable/shared/literal_library.py +0 -0
  67. {statable-3.5.2 → statable-3.5.3}/statable/shared/role_function_library.py +0 -0
  68. {statable-3.5.2 → statable-3.5.3}/statable/smoke.py +0 -0
  69. {statable-3.5.2 → statable-3.5.3}/statable/state_machine.py +0 -0
  70. {statable-3.5.2 → statable-3.5.3}/statable/xml_io.py +0 -0
  71. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/dependency_links.txt +0 -0
  72. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/entry_points.txt +0 -0
  73. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/requires.txt +0 -0
  74. {statable-3.5.2 → statable-3.5.3}/statable.egg-info/top_level.txt +0 -0
  75. {statable-3.5.2 → statable-3.5.3}/statable_gui/__init__.py +0 -0
  76. {statable-3.5.2 → statable-3.5.3}/statable_gui/action_edit_dialog.py +0 -0
  77. {statable-3.5.2 → statable-3.5.3}/statable_gui/code_generation_dialog.py +0 -0
  78. {statable-3.5.2 → statable-3.5.3}/statable_gui/code_generation_settings_dialog.py +0 -0
  79. {statable-3.5.2 → statable-3.5.3}/statable_gui/common_widgets.py +0 -0
  80. {statable-3.5.2 → statable-3.5.3}/statable_gui/condition_builder_dialog.py +0 -0
  81. {statable-3.5.2 → statable-3.5.3}/statable_gui/condition_edit_dialog.py +0 -0
  82. {statable-3.5.2 → statable-3.5.3}/statable_gui/condition_template_dialog.py +0 -0
  83. {statable-3.5.2 → statable-3.5.3}/statable_gui/config.py +0 -0
  84. {statable-3.5.2 → statable-3.5.3}/statable_gui/dialogs.py +0 -0
  85. {statable-3.5.2 → statable-3.5.3}/statable_gui/event_definition_dialog.py +0 -0
  86. {statable-3.5.2 → statable-3.5.3}/statable_gui/event_delivery_settings_dialog.py +0 -0
  87. {statable-3.5.2 → statable-3.5.3}/statable_gui/event_queue_dialog.py +0 -0
  88. {statable-3.5.2 → statable-3.5.3}/statable_gui/fs_cleanup.py +0 -0
  89. {statable-3.5.2 → statable-3.5.3}/statable_gui/global_defs.py +0 -0
  90. {statable-3.5.2 → statable-3.5.3}/statable_gui/global_defs_dialog.py +0 -0
  91. {statable-3.5.2 → statable-3.5.3}/statable_gui/i18n/__init__.py +0 -0
  92. {statable-3.5.2 → statable-3.5.3}/statable_gui/interrupt_handler_edit_dialog.py +0 -0
  93. {statable-3.5.2 → statable-3.5.3}/statable_gui/layer_settings_dialog.py +0 -0
  94. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/__init__.py +0 -0
  95. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/condition_library.py +0 -0
  96. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/literal_library.py +0 -0
  97. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/literal_management_dialog.py +0 -0
  98. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/role_function_edit_dialog.py +0 -0
  99. {statable-3.5.2 → statable-3.5.3}/statable_gui/libcntrl/role_function_library.py +0 -0
  100. {statable-3.5.2 → statable-3.5.3}/statable_gui/literal_definition_dialog.py +0 -0
  101. {statable-3.5.2 → statable-3.5.3}/statable_gui/logger.py +0 -0
  102. {statable-3.5.2 → statable-3.5.3}/statable_gui/main_window.py +0 -0
  103. {statable-3.5.2 → statable-3.5.3}/statable_gui/matrix_table.py +0 -0
  104. {statable-3.5.2 → statable-3.5.3}/statable_gui/new_project_templates.py +0 -0
  105. {statable-3.5.2 → statable-3.5.3}/statable_gui/new_project_wizard.py +0 -0
  106. {statable-3.5.2 → statable-3.5.3}/statable_gui/output_warning.py +0 -0
  107. {statable-3.5.2 → statable-3.5.3}/statable_gui/preference_keys.py +0 -0
  108. {statable-3.5.2 → statable-3.5.3}/statable_gui/preferences.py +0 -0
  109. {statable-3.5.2 → statable-3.5.3}/statable_gui/role_function_dialog.py +0 -0
  110. {statable-3.5.2 → statable-3.5.3}/statable_gui/sample_data.py +0 -0
  111. {statable-3.5.2 → statable-3.5.3}/statable_gui/state_actions_dialog.py +0 -0
  112. {statable-3.5.2 → statable-3.5.3}/statable_gui/symbol_picker.py +0 -0
  113. {statable-3.5.2 → statable-3.5.3}/statable_gui/traceball.py +0 -0
  114. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/__init__.py +0 -0
  115. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/actions_tab.py +0 -0
  116. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/code_widget.py +0 -0
  117. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/coverage_analyzer.py +0 -0
  118. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/dialog.py +0 -0
  119. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/draft.py +0 -0
  120. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/edit_dialogs.py +0 -0
  121. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/overview_tab.py +0 -0
  122. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/palette_widget.py +0 -0
  123. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/relations_edit_dialog.py +0 -0
  124. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/relations_tab.py +0 -0
  125. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/system_global_dialog.py +0 -0
  126. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/transition_actions_dialog.py +0 -0
  127. {statable-3.5.2 → statable-3.5.3}/statable_gui/transition_editor_direct/transitions_tab.py +0 -0
  128. {statable-3.5.2 → statable-3.5.3}/statable_gui/validation_dialog.py +0 -0
  129. {statable-3.5.2 → statable-3.5.3}/statable_gui/widgets.py +0 -0
  130. {statable-3.5.2 → statable-3.5.3}/tests/test_ci_registration.py +0 -0
  131. {statable-3.5.2 → statable-3.5.3}/tests/test_readme_consistency.py +0 -0
  132. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p1.py +0 -0
  133. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_10.py +0 -0
  134. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_2.py +0 -0
  135. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_5.py +0 -0
  136. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_6.py +0 -0
  137. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_7.py +0 -0
  138. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_8.py +0 -0
  139. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p12_9.py +0 -0
  140. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p2.py +0 -0
  141. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p3.py +0 -0
  142. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p4a.py +0 -0
  143. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_2_p4b.py +0 -0
  144. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_3_p1.py +0 -0
  145. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_4_p1_merge.py +0 -0
  146. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p1.py +0 -0
  147. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p10.py +0 -0
  148. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p11.py +0 -0
  149. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p2.py +0 -0
  150. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p3.py +0 -0
  151. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p4.py +0 -0
  152. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p5.py +0 -0
  153. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p6.py +0 -0
  154. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p8.py +0 -0
  155. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_5_p9.py +0 -0
  156. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_6_p1.py +0 -0
  157. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_6_p2.py +0 -0
  158. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_6_p3.py +0 -0
  159. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_7_p3.py +0 -0
  160. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_7_p4.py +0 -0
  161. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_7_p5_validation.py +0 -0
  162. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_8_p1_ai_prompt.py +0 -0
  163. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_8_p2_response_parser.py +0 -0
  164. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_8_p3_response_validator.py +0 -0
  165. {statable-3.5.2 → statable-3.5.3}/tests/test_v2_8_p4_gui_integration.py +0 -0
  166. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_0_g1_shared.py +0 -0
  167. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_0_s1_packaging.py +0 -0
  168. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_0_s2_public_api.py +0 -0
  169. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_0_s3_cli.py +0 -0
  170. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_0_s4_docs.py +0 -0
  171. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_2_s1_wizard.py +0 -0
  172. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_2_s2_signals.py +0 -0
  173. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_4_0_signal_safety.py +0 -0
  174. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_4_0_state_actions.py +0 -0
  175. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_5_1_eclipse_installer.py +0 -0
  176. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_5_gui_output_clean.py +0 -0
  177. {statable-3.5.2 → statable-3.5.3}/tests/test_v3_5_s1_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statable
3
- Version: 3.5.2
3
+ Version: 3.5.3
4
4
  Summary: MISRA C:2012-aware state machine design and C code generation for embedded systems
5
5
  Author: Hideki Akashi
6
6
  License-Expression: Apache-2.0
@@ -8,7 +8,7 @@ StaTable code generation layer
8
8
  so it is not re-exported at top level (to reduce startup time)
9
9
  """
10
10
 
11
- __version__ = "3.5.2"
11
+ __version__ = "3.5.3"
12
12
 
13
13
  __all__ = [
14
14
  'CCodeGenerator',
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "statable"
9
- version = "3.5.2"
9
+ version = "3.5.3"
10
10
  description = "MISRA C:2012-aware state machine design and C code generation for embedded systems"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -43,7 +43,7 @@ from .global_defs import (
43
43
  StructMemberDef,
44
44
  )
45
45
 
46
- __version__ = "3.5.2"
46
+ __version__ = "3.5.3"
47
47
 
48
48
  __all__ = [
49
49
  # model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statable
3
- Version: 3.5.2
3
+ Version: 3.5.3
4
4
  Summary: MISRA C:2012-aware state machine design and C code generation for embedded systems
5
5
  Author: Hideki Akashi
6
6
  License-Expression: Apache-2.0
@@ -170,5 +170,6 @@ tests/test_v3_2_s2_signals.py
170
170
  tests/test_v3_4_0_signal_safety.py
171
171
  tests/test_v3_4_0_state_actions.py
172
172
  tests/test_v3_5_1_eclipse_installer.py
173
+ tests/test_v3_5_3_eclipse_installer_deep.py
173
174
  tests/test_v3_5_gui_output_clean.py
174
175
  tests/test_v3_5_s1_smoke.py
@@ -0,0 +1,337 @@
1
+ # tests/test_v3_5_3_eclipse_installer_deep.py
2
+ """
3
+ StaTable v3.5.3 Eclipse Installer deep test suite (L1+L2)
4
+
5
+ L1: Template integrity
6
+ - No BOM, LF-only
7
+ - Placeholder syntax @@KEY@@
8
+ - Valid XML for .project / .cproject
9
+ - Makefile tab chars
10
+ - Required templates present
11
+ - linker.ld / startup.s / syscalls.c / main_demo.c content
12
+
13
+ L2: core.install edge cases
14
+ - Custom generated_subdir
15
+ - with_* flags all off
16
+ - Idempotency
17
+ - overwrite_generated=False
18
+ - Empty output_dir
19
+ - Deep nested structure
20
+ - Unicode filename
21
+ - Same filename in different subdirs
22
+ - with_samples=True
23
+ - Missing project_root error
24
+ """
25
+ import os
26
+ import re
27
+ import stat
28
+ import sys
29
+ import tempfile
30
+ import xml.etree.ElementTree as ET
31
+ from pathlib import Path
32
+
33
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
34
+
35
+ _total = 0
36
+ _passed = 0
37
+ _failed = 0
38
+
39
+
40
+ def check(label, condition):
41
+ global _total, _passed, _failed
42
+ _total += 1
43
+ if condition:
44
+ _passed += 1
45
+ print(f" [PASS] {label}")
46
+ else:
47
+ _failed += 1
48
+ print(f" [FAIL] {label}")
49
+
50
+
51
+ def section(title):
52
+ print()
53
+ print(f"[{title}]")
54
+
55
+
56
+ ROOT = Path(__file__).resolve().parent.parent
57
+ EI = ROOT / "eclipse_installer"
58
+ TPL = EI / "templates"
59
+
60
+ # ======================================================================
61
+ # L1: Template integrity
62
+ # ======================================================================
63
+ section("L1 template integrity")
64
+
65
+ # 1-1: No BOM
66
+ for f in TPL.rglob("*"):
67
+ if f.is_file():
68
+ data = f.read_bytes()
69
+ check(f"no BOM: {f.relative_to(TPL)}",
70
+ not data.startswith(b"\xef\xbb\xbf"))
71
+
72
+ # 1-2: LF-only
73
+ for f in TPL.rglob("*"):
74
+ if f.is_file():
75
+ data = f.read_bytes()
76
+ check(f"LF-only: {f.relative_to(TPL)}", b"\r\n" not in data)
77
+
78
+ # 1-3: Placeholder syntax @@KEY@@ (meta + actual)
79
+ _placeholder_re = re.compile(r"(?<!@)@([A-Z_]+)@(?!@)")
80
+ check("placeholder regex detects single-@ (meta)",
81
+ bool(_placeholder_re.search("X @FOO@ Y")))
82
+ check("placeholder regex allows @@ (meta)",
83
+ not _placeholder_re.search("X @@FOO@@ Y"))
84
+
85
+ for f in TPL.rglob("*"):
86
+ if not f.is_file():
87
+ continue
88
+ try:
89
+ t = f.read_text(encoding="utf-8")
90
+ except UnicodeDecodeError:
91
+ continue
92
+ bad = _placeholder_re.findall(t)
93
+ check(f"placeholder syntax: {f.relative_to(TPL)}", not bad)
94
+
95
+ # 1-4: Valid XML
96
+ for name in ("project.tmpl", "cproject.tmpl"):
97
+ p = TPL / name
98
+ try:
99
+ ET.fromstring(p.read_text(encoding="utf-8"))
100
+ check(f"valid XML: {name}", True)
101
+ except ET.ParseError as e:
102
+ check(f"valid XML: {name} ({e})", False)
103
+
104
+ # 1-5: Makefile tab bytes
105
+ mk_bytes = (TPL / "Makefile.tmpl").read_bytes()
106
+ check("Makefile.tmpl has >= 5 tab bytes", mk_bytes.count(9) >= 5)
107
+ mk_text = (TPL / "Makefile.tmpl").read_text(encoding="utf-8")
108
+ tab_lines = sum(1 for l in mk_text.split("\n") if l.startswith("\t"))
109
+ check("Makefile.tmpl has >= 5 tab-prefixed lines", tab_lines >= 5)
110
+
111
+ # 1-6: Required templates present
112
+ required = [
113
+ "startup_stm32f405.s", "linker_stm32f405.ld", "syscalls.c",
114
+ "main_demo.c", "Makefile.tmpl", "project.tmpl", "cproject.tmpl",
115
+ "settings/org.eclipse.core.resources.prefs",
116
+ "settings/language.settings.xml",
117
+ ]
118
+ for rel in required:
119
+ check(f"template present: {rel}", (TPL / rel).is_file())
120
+
121
+ # 1-7: linker.ld content
122
+ ld_text = (TPL / "linker_stm32f405.ld").read_text(encoding="utf-8")
123
+ check("linker.ld ENTRY(Reset_Handler)",
124
+ "ENTRY(Reset_Handler)" in ld_text)
125
+ check("linker.ld FLASH 0x08000000",
126
+ "ORIGIN = 0x08000000" in ld_text)
127
+ check("linker.ld RAM 0x20000000",
128
+ "ORIGIN = 0x20000000" in ld_text)
129
+
130
+ # 1-8: startup.s content
131
+ st_text = (TPL / "startup_stm32f405.s").read_text(encoding="utf-8")
132
+ check("startup.s Reset_Handler", "Reset_Handler:" in st_text)
133
+ check("startup.s g_pfnVectors", "g_pfnVectors:" in st_text)
134
+
135
+ # 1-9: syscalls.c newlib stubs
136
+ sc_text = (TPL / "syscalls.c").read_text(encoding="utf-8")
137
+ for stub in ("_write", "_read", "_sbrk", "_close", "_fstat", "_exit"):
138
+ check(f"syscalls.c has {stub}", stub in sc_text)
139
+
140
+ # 1-10: main_demo.c weak hooks
141
+ md_text = (TPL / "main_demo.c").read_text(encoding="utf-8")
142
+ check("main_demo.c app_init weak",
143
+ "__attribute__((weak)) void app_init" in md_text)
144
+ check("main_demo.c app_step weak",
145
+ "__attribute__((weak)) void app_step" in md_text)
146
+
147
+ # ======================================================================
148
+ # L2: core.install edge cases
149
+ # ======================================================================
150
+ section("L2 core.install edge cases")
151
+
152
+ from eclipse_installer.core import InstallOptions, install
153
+
154
+
155
+ def _mkfile(root, rel, content="x"):
156
+ full = os.path.join(root, rel)
157
+ parent = os.path.dirname(full)
158
+ if parent:
159
+ os.makedirs(parent, exist_ok=True)
160
+ with open(full, "w", encoding="utf-8") as f:
161
+ f.write(content)
162
+ return os.path.abspath(full)
163
+
164
+
165
+ # 2-1: Custom generated_subdir
166
+ with tempfile.TemporaryDirectory() as out_td, \
167
+ tempfile.TemporaryDirectory() as proj_td:
168
+ _mkfile(out_td, "a.c", "// a")
169
+ opts = InstallOptions(
170
+ project_root=Path(proj_td),
171
+ output_dir=Path(out_td),
172
+ generated_subdir="my/custom/dir",
173
+ )
174
+ res = install(opts)
175
+ check("2-1 custom subdir: no errors", not res.errors)
176
+ check("2-1 custom subdir: file placed",
177
+ (Path(proj_td) / "my" / "custom" / "dir" / "a.c").is_file())
178
+
179
+
180
+ # 2-2: All with_* off -> only generated C
181
+ with tempfile.TemporaryDirectory() as out_td, \
182
+ tempfile.TemporaryDirectory() as proj_td:
183
+ _mkfile(out_td, "x.c", "// x")
184
+ opts = InstallOptions(
185
+ project_root=Path(proj_td),
186
+ output_dir=Path(out_td),
187
+ with_startup=False, with_linker=False, with_syscalls=False,
188
+ with_main=False, with_makefile=False, with_eclipse_project=False,
189
+ )
190
+ res = install(opts)
191
+ check("2-2 all off: no errors", not res.errors)
192
+ check("2-2 all off: no main.c",
193
+ not (Path(proj_td) / "main.c").exists())
194
+ check("2-2 all off: no .project",
195
+ not (Path(proj_td) / ".project").exists())
196
+ check("2-2 all off: generated C copied",
197
+ (Path(proj_td) / "src" / "statable_generated" / "x.c").is_file())
198
+
199
+
200
+ # 2-3: Idempotency
201
+ with tempfile.TemporaryDirectory() as out_td, \
202
+ tempfile.TemporaryDirectory() as proj_td:
203
+ _mkfile(out_td, "x.c", "// x")
204
+ opts = InstallOptions(
205
+ project_root=Path(proj_td),
206
+ output_dir=Path(out_td),
207
+ )
208
+ res1 = install(opts)
209
+ res2 = install(opts)
210
+ check("2-3 idempotent: 1st no errors", not res1.errors)
211
+ check("2-3 idempotent: 2nd no errors", not res2.errors)
212
+ # 1st install: everything fresh (installed = all)
213
+ # 2nd install: generated C overwritten (installed), scaffolding
214
+ # skipped (overwrite_scaffolding=False default)
215
+ check("2-3 idempotent: 2nd has fewer installed",
216
+ len(res2.installed) < len(res1.installed))
217
+ check("2-3 idempotent: 2nd skipped scaffolding (>= 1)",
218
+ len(res2.skipped) >= 1)
219
+ check("2-3 idempotent: 2nd installed + skipped == 1st installed",
220
+ len(res2.installed) + len(res2.skipped) == len(res1.installed))
221
+
222
+
223
+ # 2-4: overwrite_generated=False preserves generated C
224
+ with tempfile.TemporaryDirectory() as out_td, \
225
+ tempfile.TemporaryDirectory() as proj_td:
226
+ _mkfile(out_td, "x.c", "// original")
227
+ opts = InstallOptions(
228
+ project_root=Path(proj_td),
229
+ output_dir=Path(out_td),
230
+ overwrite_generated=False,
231
+ )
232
+ install(opts)
233
+ target = Path(proj_td) / "src" / "statable_generated" / "x.c"
234
+ target.write_text("// user modified", encoding="utf-8")
235
+ res2 = install(opts)
236
+ check("2-4 overwrite=False: user file preserved",
237
+ target.read_text(encoding="utf-8") == "// user modified")
238
+ check("2-4 overwrite=False: skipped reported",
239
+ len(res2.skipped) >= 1)
240
+
241
+
242
+ # 2-5: Empty output_dir
243
+ with tempfile.TemporaryDirectory() as out_td, \
244
+ tempfile.TemporaryDirectory() as proj_td:
245
+ opts = InstallOptions(
246
+ project_root=Path(proj_td),
247
+ output_dir=Path(out_td),
248
+ )
249
+ res = install(opts)
250
+ check("2-5 empty output: no errors", not res.errors)
251
+ src_dir = Path(proj_td) / "src"
252
+ gen_c = list(src_dir.rglob("*.c")) if src_dir.exists() else []
253
+ check("2-5 empty output: 0 generated C", not gen_c)
254
+
255
+ # 2-6: Deeply nested structure preserved
256
+ with tempfile.TemporaryDirectory() as out_td, \
257
+ tempfile.TemporaryDirectory() as proj_td:
258
+ _mkfile(out_td, "a/b/c/d/e/deep.c", "// deep")
259
+ opts = InstallOptions(
260
+ project_root=Path(proj_td),
261
+ output_dir=Path(out_td),
262
+ )
263
+ install(opts)
264
+ check("2-6 deep nested preserved",
265
+ (Path(proj_td) / "src" / "statable_generated"
266
+ / "a" / "b" / "c" / "d" / "e" / "deep.c").is_file())
267
+
268
+
269
+ # 2-7: Unicode filename
270
+ with tempfile.TemporaryDirectory() as out_td, \
271
+ tempfile.TemporaryDirectory() as proj_td:
272
+ _mkfile(out_td, "\u65e5\u672c\u8a9e.c", "// unicode")
273
+ opts = InstallOptions(
274
+ project_root=Path(proj_td),
275
+ output_dir=Path(out_td),
276
+ )
277
+ res = install(opts)
278
+ check("2-7 unicode: no errors", not res.errors)
279
+ check("2-7 unicode: filename preserved",
280
+ (Path(proj_td) / "src" / "statable_generated"
281
+ / "\u65e5\u672c\u8a9e.c").is_file())
282
+
283
+
284
+ # 2-8: Same filename in different subdirs
285
+ with tempfile.TemporaryDirectory() as out_td, \
286
+ tempfile.TemporaryDirectory() as proj_td:
287
+ _mkfile(out_td, "a/x.c", "// a")
288
+ _mkfile(out_td, "b/x.c", "// b")
289
+ opts = InstallOptions(
290
+ project_root=Path(proj_td),
291
+ output_dir=Path(out_td),
292
+ )
293
+ res = install(opts)
294
+ check("2-8 dup name: no errors", not res.errors)
295
+ a = (Path(proj_td) / "src" / "statable_generated" / "a" / "x.c")
296
+ b = (Path(proj_td) / "src" / "statable_generated" / "b" / "x.c")
297
+ check("2-8 dup name: a/x.c exists", a.is_file())
298
+ check("2-8 dup name: b/x.c exists", b.is_file())
299
+ check("2-8 dup name: content distinct",
300
+ a.read_text(encoding="utf-8") == "// a"
301
+ and b.read_text(encoding="utf-8") == "// b")
302
+
303
+
304
+ # 2-9: with_samples=True copies XML
305
+ with tempfile.TemporaryDirectory() as out_td, \
306
+ tempfile.TemporaryDirectory() as proj_td:
307
+ _mkfile(out_td, "x.c", "// x")
308
+ _mkfile(out_td, "sample.xml", "<xml/>")
309
+ opts = InstallOptions(
310
+ project_root=Path(proj_td),
311
+ output_dir=Path(out_td),
312
+ with_samples=True,
313
+ )
314
+ res = install(opts)
315
+ check("2-9 samples: no errors", not res.errors)
316
+ check("2-9 samples: xml copied",
317
+ (Path(proj_td) / "samples" / "sample.xml").is_file())
318
+
319
+
320
+ # 2-10: Missing project_root -> error
321
+ with tempfile.TemporaryDirectory() as td:
322
+ res = install(InstallOptions(
323
+ project_root=Path(td) / "no_such",
324
+ output_dir=Path(td),
325
+ ))
326
+ check("2-10 missing project_root: error", len(res.errors) >= 1)
327
+
328
+
329
+ # ======================================================================
330
+ # Summary
331
+ # ======================================================================
332
+ print()
333
+ print("=" * 70)
334
+ print(f" TOTAL: {_total} PASSED: {_passed} FAILED: {_failed}")
335
+ print("=" * 70)
336
+
337
+ sys.exit(0 if _failed == 0 else 1)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes