ceedling 1.1.0 → 1.1.2

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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +10 -7
  4. data/assets/broken_uncovered_file.c +13 -0
  5. data/assets/test_example_file_2.c +22 -0
  6. data/assets/tests_with_partials_coverage/src/blanks.c +10 -0
  7. data/assets/tests_with_partials_coverage/src/blanks.h +1 -0
  8. data/assets/tests_with_partials_coverage/src/counter.c +12 -0
  9. data/assets/tests_with_partials_coverage/src/counter.h +1 -0
  10. data/assets/tests_with_partials_coverage/src/decorators.c +11 -0
  11. data/assets/tests_with_partials_coverage/src/decorators.h +4 -0
  12. data/assets/tests_with_partials_coverage/test/test_blanks.c +8 -0
  13. data/assets/tests_with_partials_coverage/test/test_counter.c +9 -0
  14. data/assets/tests_with_partials_coverage/test/test_decorators.c +8 -0
  15. data/docs/Changelog.md +80 -38
  16. data/docs/KnownIssues.md +9 -0
  17. data/docs/ReleaseNotes.md +5 -1
  18. data/docs/mkdocs/getting-started/installation.md +8 -1
  19. data/docs/mkdocs/getting-started/quick-start.md +20 -2
  20. data/docs/mkdocs/help.md +2 -2
  21. data/docs/mkdocs/index.md +13 -6
  22. data/docs/mkdocs/overview/build-system.md +4 -4
  23. data/docs/mkdocs/plugins/bullseye.md +6 -0
  24. data/docs/mkdocs/plugins/command-hooks.md +20 -15
  25. data/docs/mkdocs/plugins/gcov/gcovr.md +130 -109
  26. data/docs/mkdocs/plugins/gcov/index.md +6 -0
  27. data/docs/mkdocs/plugins/index.md +40 -0
  28. data/docs/mkdocs/plugins/valgrind.md +6 -0
  29. data/docs/mkdocs/testing-guide/partials/index.md +22 -13
  30. data/lib/ceedling/c_extractor/c_extractor_declarations.rb +3 -1
  31. data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +20 -7
  32. data/lib/ceedling/config/configurator.rb +2 -1
  33. data/lib/ceedling/generators/generator_partials.rb +13 -3
  34. data/lib/ceedling/objects.yml +1 -0
  35. data/lib/ceedling/preprocess/preprocessinator.rb +3 -2
  36. data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +37 -15
  37. data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +23 -15
  38. data/lib/ceedling/test_invoker/test_build_executor.rb +3 -4
  39. data/lib/ceedling/test_invoker/test_build_planner.rb +0 -2
  40. data/lib/ceedling/test_invoker/test_invoker_types.rb +1 -1
  41. data/lib/ceedling/tool_executor.rb +5 -0
  42. data/lib/version.rb +1 -1
  43. data/plugins/command_hooks/lib/command_hooks.rb +4 -0
  44. data/plugins/gcov/lib/console_reportinator.rb +3 -1
  45. data/plugins/gcov/lib/gcov.rb +3 -3
  46. data/plugins/gcov/lib/gcov_reportinator.rb +4 -0
  47. data/plugins/gcov/lib/gcovr_reportinator.rb +61 -14
  48. data/plugins/gcov/lib/reportgenerator_reportinator.rb +3 -1
  49. data/site-local/404.html +3 -3
  50. data/site-local/assets/javascripts/{bundle.79ae519e.min.js → bundle.d7400e89.min.js} +2 -2
  51. data/site-local/assets/javascripts/{bundle.79ae519e.min.js.map → bundle.d7400e89.min.js.map} +2 -2
  52. data/site-local/assets/stylesheets/main.ec1eaa64.min.css +1 -0
  53. data/site-local/assets/stylesheets/{main.484c7ddc.min.css.map → main.ec1eaa64.min.css.map} +1 -1
  54. data/site-local/configuration/environment-vars.html +3 -3
  55. data/site-local/configuration/global-collections.html +3 -3
  56. data/site-local/configuration/index.html +3 -3
  57. data/site-local/configuration/loading.html +3 -3
  58. data/site-local/configuration/mixins.html +3 -3
  59. data/site-local/configuration/parallel-builds.html +3 -3
  60. data/site-local/configuration/project-file.html +3 -3
  61. data/site-local/configuration/reference/cexception.html +3 -3
  62. data/site-local/configuration/reference/cmock.html +3 -3
  63. data/site-local/configuration/reference/defines.html +3 -3
  64. data/site-local/configuration/reference/environment.html +3 -3
  65. data/site-local/configuration/reference/extension.html +3 -3
  66. data/site-local/configuration/reference/files.html +3 -3
  67. data/site-local/configuration/reference/flags.html +3 -3
  68. data/site-local/configuration/reference/index.html +3 -3
  69. data/site-local/configuration/reference/libraries.html +3 -3
  70. data/site-local/configuration/reference/mixins.html +3 -3
  71. data/site-local/configuration/reference/paths.html +3 -3
  72. data/site-local/configuration/reference/plugins.html +3 -3
  73. data/site-local/configuration/reference/project.html +3 -3
  74. data/site-local/configuration/reference/release-build.html +3 -3
  75. data/site-local/configuration/reference/test-build.html +3 -3
  76. data/site-local/configuration/reference/test-runner.html +3 -3
  77. data/site-local/configuration/reference/tools.html +3 -3
  78. data/site-local/configuration/reference/unity.html +3 -3
  79. data/site-local/configuration/which-ceedling.html +3 -3
  80. data/site-local/development/index.html +3 -3
  81. data/site-local/development/plugins/configuration.html +3 -3
  82. data/site-local/development/plugins/index.html +3 -3
  83. data/site-local/development/plugins/plugin-subclass.html +3 -3
  84. data/site-local/development/plugins/rake-tasks.html +3 -3
  85. data/site-local/development/workflow.html +3 -3
  86. data/site-local/getting-started/command-line.html +3 -3
  87. data/site-local/getting-started/example-projects/cipher-quest.html +3 -3
  88. data/site-local/getting-started/example-projects/index.html +3 -3
  89. data/site-local/getting-started/example-projects/temp-sensor.html +3 -3
  90. data/site-local/getting-started/example-projects/wondrous-forest.html +3 -3
  91. data/site-local/getting-started/index.html +3 -3
  92. data/site-local/getting-started/installation.html +12 -4
  93. data/site-local/getting-started/quick-start.html +18 -4
  94. data/site-local/help.html +4 -4
  95. data/site-local/index.html +13 -8
  96. data/site-local/overview/build-system.html +6 -6
  97. data/site-local/overview/index.html +3 -3
  98. data/site-local/overview/test-environments.html +3 -3
  99. data/site-local/overview/tools-and-frameworks.html +3 -3
  100. data/site-local/plugins/beep.html +3 -3
  101. data/site-local/plugins/bullseye.html +9 -3
  102. data/site-local/plugins/command-hooks.html +42 -38
  103. data/site-local/plugins/compile-commands-json-db.html +3 -3
  104. data/site-local/plugins/cppcheck.html +3 -3
  105. data/site-local/plugins/dependencies.html +3 -3
  106. data/site-local/plugins/fff.html +3 -3
  107. data/site-local/plugins/gcov/examples.html +3 -3
  108. data/site-local/plugins/gcov/gcovr.html +325 -227
  109. data/site-local/plugins/gcov/index.html +9 -3
  110. data/site-local/plugins/gcov/overview.html +3 -3
  111. data/site-local/plugins/gcov/reportgenerator.html +3 -3
  112. data/site-local/plugins/gcov/reporting.html +3 -3
  113. data/site-local/plugins/gcov/setup.html +3 -3
  114. data/site-local/plugins/gcov/tool-versions.html +3 -3
  115. data/site-local/plugins/gcov/troubleshooting.html +3 -3
  116. data/site-local/plugins/index.html +79 -15
  117. data/site-local/plugins/module-generator.html +3 -3
  118. data/site-local/plugins/report-build-warnings-log.html +3 -3
  119. data/site-local/plugins/report-tests-gtestlike-stdout.html +3 -3
  120. data/site-local/plugins/report-tests-ide-stdout.html +3 -3
  121. data/site-local/plugins/report-tests-log-factory.html +3 -3
  122. data/site-local/plugins/report-tests-pretty-stdout.html +3 -3
  123. data/site-local/plugins/report-tests-raw-output-log.html +3 -3
  124. data/site-local/plugins/report-tests-teamcity-stdout.html +3 -3
  125. data/site-local/plugins/valgrind.html +9 -3
  126. data/site-local/project/release-history.html +3 -3
  127. data/site-local/project/upgrade.html +3 -3
  128. data/site-local/reference/build-directives.html +3 -3
  129. data/site-local/reference/command-line.html +3 -3
  130. data/site-local/reference/environment-vars.html +3 -3
  131. data/site-local/reference/gcov-plugin.html +3 -3
  132. data/site-local/reference/global-collections.html +3 -3
  133. data/site-local/reference/index.html +3 -3
  134. data/site-local/reference/partials-macros.html +3 -3
  135. data/site-local/reference/project-configuration.html +3 -3
  136. data/site-local/sitemap.xml.gz +0 -0
  137. data/site-local/snapshot/examples/cipher_quest/index.html +3 -3
  138. data/site-local/snapshot/examples/temp_sensor/index.html +3 -3
  139. data/site-local/snapshot/examples/wondrous_forest/index.html +3 -3
  140. data/site-local/testing-guide/build-directives.html +3 -3
  141. data/site-local/testing-guide/conventions.html +3 -3
  142. data/site-local/testing-guide/frameworks.html +3 -3
  143. data/site-local/testing-guide/index.html +3 -3
  144. data/site-local/testing-guide/partials/configuration.html +3 -3
  145. data/site-local/testing-guide/partials/conventions.html +3 -3
  146. data/site-local/testing-guide/partials/directives.html +3 -3
  147. data/site-local/testing-guide/partials/example.html +3 -3
  148. data/site-local/testing-guide/partials/index.html +25 -16
  149. data/site-local/testing-guide/partials/overview.html +3 -3
  150. data/site-local/testing-guide/partials/variables.html +3 -3
  151. data/site-local/testing-guide/test-cases.html +3 -3
  152. data/site-local/testing-guide/test-sample.html +3 -3
  153. data/site-local/testing-guide/test-suite-anatomy.html +3 -3
  154. data/spec/support/system/system_context.rb +3 -1
  155. data/spec/system/gcov_deployment_spec.rb +20 -6
  156. data/spec/system/support/gcov_common_test_cases.rb +34 -45
  157. data/spec/system/support/gcov_partials_test_cases.rb +116 -0
  158. data/spec/units/c_extractor/c_extractor_declarations_spec.rb +128 -0
  159. data/spec/units/c_extractor/c_extractor_definitions_spec.rb +7 -0
  160. data/spec/units/c_extractor/c_extractor_integration_spec.rb +17 -0
  161. data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +46 -0
  162. data/spec/units/generators/generator_partials_spec.rb +32 -6
  163. data/spec/units/preprocess/preprocessinator_file_assembler_spec.rb +250 -1
  164. data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +151 -1
  165. data/spec/units/test_build_executor_spec.rb +114 -0
  166. data/vendor/unity/auto/generate_test_runner.rb +55 -5
  167. data/vendor/unity/src/unity.h +1 -1
  168. data/vendor/unity/test/testdata/testRunnerGeneratorEmpty.c +13 -0
  169. data/vendor/unity/test/tests/test_generate_test_runner.rb +11 -0
  170. metadata +22 -7
  171. data/site-local/assets/stylesheets/main.484c7ddc.min.css +0 -1
  172. data/vendor/unity/auto/run_test.erb +0 -37
@@ -0,0 +1,116 @@
1
+ # =========================================================================
2
+ # Ceedling - Test-Centered Build System for C
3
+ # ThrowTheSwitch.org
4
+ # Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
5
+ # SPDX-License-Identifier: MIT
6
+ # =========================================================================
7
+
8
+ require 'rexml/document'
9
+ require_relative 'gcov_helpers'
10
+
11
+ module GcovPartialsTestCases
12
+ include GcovHelpers
13
+
14
+ # Runs `ceedling gcov:all` with Partials + Cobertura XML reporting enabled, then
15
+ # returns { line_number => hits } for `source_relpath` (e.g. 'src/blanks.c').
16
+ def partials_cobertura_hits_by_line(source_relpath)
17
+ @c.merge_project_yml_for_test({
18
+ :project => { :use_partials => true },
19
+ :gcov => { :reports => ['Cobertura'] }
20
+ })
21
+
22
+ output = @c.ceedling_build_exec("gcov:all")
23
+ expect(@c.last_exit_status).to eq(0)
24
+
25
+ cobertura_path = File.join('build', 'artifacts', 'gcov', 'gcovr', 'GcovCoverageCobertura.xml')
26
+ doc = REXML::Document.new(File.read(cobertura_path))
27
+
28
+ doc.elements.to_a("//class[@filename='#{source_relpath}']/lines/line").each_with_object({}) do |el, h|
29
+ h[el.attributes['number'].to_i] = el.attributes['hits'].to_i
30
+ end
31
+ end
32
+
33
+ def gcov_partials_coverage_blank_lines_in_function_body
34
+ @c.with_context do
35
+ Dir.chdir @proj_name do
36
+ prep_project_yml_for_coverage
37
+
38
+ # Multiple consecutive blank lines inside a function body -- the same
39
+ # shape a stripped multi-line comment leaves behind -- sit between the
40
+ # opening brace and the one executed statement. Each function in a
41
+ # generated Partial gets exactly one #line directive, at its own first
42
+ # line; every line after that is trusted to match the original source
43
+ # 1:1. If any lines partway through the body go missing during
44
+ # reconstruction, gcov's line attribution for everything after that
45
+ # point in the function shifts up by the deficit.
46
+ asset_base = test_asset_path("tests_with_partials_coverage")
47
+ FileUtils.cp "#{asset_base}/src/blanks.h", 'src/'
48
+ FileUtils.cp "#{asset_base}/src/blanks.c", 'src/'
49
+ FileUtils.cp "#{asset_base}/test/test_blanks.c", 'test/'
50
+
51
+ hits = partials_cobertura_hits_by_line('src/blanks.c')
52
+
53
+ # `return;` is on source line 5 -- it must show as hit.
54
+ expect(hits[5]).to be > 0
55
+ # The blank lines (3-4) must not appear as executable/hit lines at all.
56
+ expect(hits[3]).to be_nil
57
+ expect(hits[4]).to be_nil
58
+ end
59
+ end
60
+ end
61
+
62
+ def gcov_partials_coverage_decorators_on_own_lines
63
+ @c.with_context do
64
+ Dir.chdir @proj_name do
65
+ prep_project_yml_for_coverage
66
+
67
+ # `static` and `inline` each occupy their own line ahead of the return
68
+ # type. Partials strips both keywords from the front of the extracted
69
+ # function text (per the docs, functions are stripped of `static` and
70
+ # `inline`) and must bump the emitted #line directive forward by
71
+ # exactly the number of lines that stripping removed -- otherwise the
72
+ # whole body, including the blank line below, reports coverage against
73
+ # the wrong original source lines.
74
+ asset_base = test_asset_path("tests_with_partials_coverage")
75
+ FileUtils.cp "#{asset_base}/src/decorators.h", 'src/'
76
+ FileUtils.cp "#{asset_base}/src/decorators.c", 'src/'
77
+ FileUtils.cp "#{asset_base}/test/test_decorators.c", 'test/'
78
+
79
+ hits = partials_cobertura_hits_by_line('src/decorators.c')
80
+
81
+ # `return;` is on source line 7 -- it must show as hit.
82
+ expect(hits[7]).to be > 0
83
+ # The blank line (6) must not appear as an executable/hit line.
84
+ expect(hits[6]).to be_nil
85
+ end
86
+ end
87
+ end
88
+
89
+ def gcov_partials_coverage_function_scope_static_promotion
90
+ @c.with_context do
91
+ Dir.chdir @proj_name do
92
+ prep_project_yml_for_coverage
93
+
94
+ # A function-scope `static` variable is replaced with a no-op and
95
+ # promoted to module scope so the Partial's testable copy of the
96
+ # function keeps the original's persistence semantics (per the docs'
97
+ # Partials creation steps). Confirms that substitution doesn't shift
98
+ # the lines after it out of alignment with their original source line
99
+ # numbers.
100
+ asset_base = test_asset_path("tests_with_partials_coverage")
101
+ FileUtils.cp "#{asset_base}/src/counter.h", 'src/'
102
+ FileUtils.cp "#{asset_base}/src/counter.c", 'src/'
103
+ FileUtils.cp "#{asset_base}/test/test_counter.c", 'test/'
104
+
105
+ hits = partials_cobertura_hits_by_line('src/counter.c')
106
+
107
+ # `count++;` (line 5) and `return count;` (line 7) must both show as
108
+ # hit, correctly separated by the blank lines around them (4 and 6).
109
+ expect(hits[5]).to be > 0
110
+ expect(hits[7]).to be > 0
111
+ expect(hits[4]).to be_nil
112
+ expect(hits[6]).to be_nil
113
+ end
114
+ end
115
+ end
116
+ end
@@ -445,6 +445,58 @@ describe CExtractorDeclarations do
445
445
  end
446
446
  end
447
447
 
448
+ context "escape sequences in string literal initializers" do
449
+ it "extracts pointer variable with escaped quote in string literal initializer" do
450
+ content = 'char* s = "say \"hi\"";'
451
+ success, variable, pos, rest = extract_variable.call(content)
452
+
453
+ expect(success).to be true
454
+ check_single(variable, name: 's', type: 'char*', text: 'char* s = "say \"hi\"";')
455
+ expect(pos).to eq(content.length)
456
+ expect(rest).to eq("")
457
+ end
458
+
459
+ it "extracts pointer variable with escaped backslash in string literal initializer" do
460
+ content = 'char* s = "C:\\\\dir";'
461
+ success, variable, pos, rest = extract_variable.call(content)
462
+
463
+ expect(success).to be true
464
+ check_single(variable, name: 's', type: 'char*', text: 'char* s = "C:\\\\dir";')
465
+ expect(pos).to eq(content.length)
466
+ expect(rest).to eq("")
467
+ end
468
+
469
+ it "extracts pointer variable with escaped newline in string literal initializer" do
470
+ content = 'char* s = "line1\\nline2";'
471
+ success, variable, pos, rest = extract_variable.call(content)
472
+
473
+ expect(success).to be true
474
+ check_single(variable, name: 's', type: 'char*', text: 'char* s = "line1\\nline2";')
475
+ expect(pos).to eq(content.length)
476
+ expect(rest).to eq("")
477
+ end
478
+
479
+ it "extracts pointer variable with hex escape in string literal initializer" do
480
+ content = 'char* s = "\\x41\\x42";'
481
+ success, variable, pos, rest = extract_variable.call(content)
482
+
483
+ expect(success).to be true
484
+ check_single(variable, name: 's', type: 'char*', text: 'char* s = "\\x41\\x42";')
485
+ expect(pos).to eq(content.length)
486
+ expect(rest).to eq("")
487
+ end
488
+
489
+ it "extracts pointer variable with octal escape in string literal initializer" do
490
+ content = 'char* s = "\\101\\102";'
491
+ success, variable, pos, rest = extract_variable.call(content)
492
+
493
+ expect(success).to be true
494
+ check_single(variable, name: 's', type: 'char*', text: 'char* s = "\\101\\102";')
495
+ expect(pos).to eq(content.length)
496
+ expect(rest).to eq("")
497
+ end
498
+ end
499
+
448
500
  context "qualified type declarations" do
449
501
  it "extracts const variable" do
450
502
  content = "const int value;"
@@ -685,6 +737,82 @@ describe CExtractorDeclarations do
685
737
  end
686
738
  end
687
739
 
740
+ context "multiline declarations" do
741
+ it "extracts array declaration whose closing brace and semicolon fall on the final line after multiline data (GH #1182)" do
742
+ content = "static uint8_t arr[10] = {\n 0x00, 0x01, 0x02, 0x03, 0x04,\n 0x05, 0x06, 0x07, 0x08, 0x09\n};"
743
+ success, variable, pos, rest = extract_variable.call(content)
744
+
745
+ expect(success).to be true
746
+ check_single(variable, name: 'arr', type: 'uint8_t', decorators: ['static'],
747
+ text: 'uint8_t arr[10] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };',
748
+ array_suffix: '[10]')
749
+ expect(pos).to eq(content.length)
750
+ expect(rest).to eq("")
751
+ end
752
+
753
+ it "extracts nested multi-dimensional array initializer whose closing braces and semicolon fall on the final line" do
754
+ content = "static int matrix[2][3] = {\n {1, 2, 3},\n {4, 5, 6}\n};"
755
+ success, variable, pos, rest = extract_variable.call(content)
756
+
757
+ expect(success).to be true
758
+ check_single(variable, name: 'matrix', type: 'int', decorators: ['static'],
759
+ text: 'int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} };',
760
+ array_suffix: '[2][3]')
761
+ expect(pos).to eq(content.length)
762
+ expect(rest).to eq("")
763
+ end
764
+
765
+ it "expands a compound declaration whose second declarator has a multiline brace initializer" do
766
+ content = "static int a, arr[3] = {\n 1,\n 2,\n 3\n};"
767
+ success, variable, pos, rest = extract_variable.call(content)
768
+
769
+ expect(success).to be true
770
+ expect(variable.length).to eq 2
771
+
772
+ expect(variable[0].name).to eq 'a'
773
+ expect(variable[0].decorators).to eq ['static']
774
+ expect(variable[0].text).to eq 'int a;'
775
+
776
+ expect(variable[1].name).to eq 'arr'
777
+ expect(variable[1].decorators).to eq ['static']
778
+ expect(variable[1].array_suffix).to eq '[3]'
779
+ expect(variable[1].text).to eq 'int arr[3] = { 1, 2, 3 };'
780
+
781
+ expect(pos).to eq(content.length)
782
+ expect(rest).to eq("")
783
+ end
784
+
785
+ it "extracts a declaration with the type and name split across lines and no braces involved" do
786
+ content = "int\n x;"
787
+ success, variable, pos, rest = extract_variable.call(content)
788
+
789
+ expect(success).to be true
790
+ check_single(variable, name: 'x', type: 'int', text: 'int x;')
791
+ expect(pos).to eq(content.length)
792
+ expect(rest).to eq("")
793
+ end
794
+
795
+ it "extracts a declaration whose initializer value falls on the line after the assignment" do
796
+ content = "int x =\n 5;"
797
+ success, variable, pos, rest = extract_variable.call(content)
798
+
799
+ expect(success).to be true
800
+ check_single(variable, name: 'x', type: 'int', text: 'int x = 5;')
801
+ expect(pos).to eq(content.length)
802
+ expect(rest).to eq("")
803
+ end
804
+
805
+ it "extracts a declaration whose terminating semicolon is alone on the line following a bracketed declarator" do
806
+ content = "int arr[10]\n;"
807
+ success, variable, pos, rest = extract_variable.call(content)
808
+
809
+ expect(success).to be true
810
+ check_single(variable, name: 'arr', type: 'int', text: 'int arr[10] ;', array_suffix: '[10]')
811
+ expect(pos).to eq(content.length)
812
+ expect(rest).to eq("")
813
+ end
814
+ end
815
+
688
816
  context "array_suffix field" do
689
817
  it "returns empty string for a scalar variable" do
690
818
  content = "static uint8 s_count;"
@@ -295,6 +295,13 @@ describe CExtractorDefinitions do
295
295
  expect(pos).to eq input.length
296
296
  end
297
297
 
298
+ it "extracts a named struct when the closing '}' and terminating ';' fall on separate lines" do
299
+ input = "struct Point {\n int x;\n}\n;\n"
300
+ result, pos = try_aggregate(input)
301
+ expect(result).to eq [true, input.chomp]
302
+ expect(pos).to eq input.length
303
+ end
304
+
298
305
  it "extracts an anonymous struct (no tag name)" do
299
306
  input = "struct { int x; int y; };\n"
300
307
  result, pos = try_aggregate(input)
@@ -561,6 +561,23 @@ describe CExtractor do
561
561
  expect( contents.variable_declarations.length ).to eq 0
562
562
  end
563
563
 
564
+ it "should extract a function definition following a #define with an escaped character in a string literal (GH #1184)" do
565
+ file_contents = <<~'CONTENTS'
566
+ #include "world.h"
567
+ #define SZ1 sizeof("\n")
568
+ void helloWorld() { return; }
569
+ CONTENTS
570
+
571
+ contents = extract_from.call(file_contents)
572
+
573
+ expect( contents.macro_definitions.length ).to eq 1
574
+ expect( contents.macro_definitions[0].text ).to eq '#define SZ1 sizeof("\n")'
575
+
576
+ expect( contents.function_definitions.length ).to eq 1
577
+ expect( contents.function_definitions[0].name ).to eq 'helloWorld'
578
+ expect( contents.function_definitions[0].body ).to eq '{ return; }'
579
+ end
580
+
564
581
  it "should consume #pragma and #include directives without storing them" do
565
582
  file_contents = <<~CONTENTS
566
583
  #pragma once
@@ -290,6 +290,45 @@ describe CExtractorPreprocessing do
290
290
  expect(pos).to eq input.length
291
291
  end
292
292
 
293
+ # --- Regression: GH #1184 — escape sequences inside a #define string literal ---
294
+ # A lone '\' preceding a string-literal escape (e.g. \n, \", \\) is not a line
295
+ # continuation and must not be mistaken for one, truncating the directive.
296
+
297
+ it "extracts a #define whose string literal contains an escaped newline" do
298
+ input = "#define SZ1 sizeof(\"\\n\")\n"
299
+ result, pos = try_directive(input)
300
+ expect(result).to eq [true, input.rstrip]
301
+ expect(pos).to eq input.length
302
+ end
303
+
304
+ it "extracts a #define whose string literal contains an escaped quote" do
305
+ input = "#define MSG \"say \\\"hi\\\"\"\n"
306
+ result, pos = try_directive(input)
307
+ expect(result).to eq [true, input.rstrip]
308
+ expect(pos).to eq input.length
309
+ end
310
+
311
+ it "extracts a #define whose string literal contains an escaped backslash" do
312
+ input = "#define PATH \"C:\\\\dir\"\n"
313
+ result, pos = try_directive(input)
314
+ expect(result).to eq [true, input.rstrip]
315
+ expect(pos).to eq input.length
316
+ end
317
+
318
+ it "extracts a #define with an escaped character and no trailing newline (EOS)" do
319
+ input = "#define SZ1 sizeof(\"\\n\")"
320
+ result, pos = try_directive(input)
321
+ expect(result).to eq [true, input]
322
+ expect(pos).to eq input.length
323
+ end
324
+
325
+ it "does not consume a following function definition after a #define with an escaped character" do
326
+ input = "#define SZ1 sizeof(\"\\n\")\nvoid helloWorld() { return; }\n"
327
+ result, pos = try_directive(input)
328
+ expect(result).to eq [true, "#define SZ1 sizeof(\"\\n\")"]
329
+ expect(pos).to eq "#define SZ1 sizeof(\"\\n\")\n".length
330
+ end
331
+
293
332
  it "stops at end of directive and does not consume following code" do
294
333
  input = "#define FOO 1\nint x = 0;"
295
334
  result, pos = try_directive(input)
@@ -483,6 +522,13 @@ describe CExtractorPreprocessing do
483
522
  expect(pos).to eq input.length
484
523
  end
485
524
 
525
+ it "handles a static assert whose terminating ';' falls on its own line after the closing ')'" do
526
+ input = "static_assert(sizeof(int) == 4, \"msg\")\n;\n"
527
+ result, pos = try_static_assert(input)
528
+ expect(result[0]).to be true
529
+ expect(pos).to eq input.length
530
+ end
531
+
486
532
  # --- Boundary behaviour ---
487
533
 
488
534
  it "stops at the ';' and does not consume following code" do
@@ -84,12 +84,14 @@ describe GeneratorPartials do
84
84
  header_file_handle = double('header_file_handle')
85
85
  source_file_handle = double('source_file_handle')
86
86
 
87
+ # Binary mode ('wb'): generated files carry embedded content whose line
88
+ # endings must reach disk unchanged, which text mode would not guarantee.
87
89
  allow(@file_wrapper).to receive(:open)
88
- .with(expected_header_filepath, 'w')
90
+ .with(expected_header_filepath, 'wb')
89
91
  .and_yield(header_file_handle)
90
92
 
91
93
  allow(@file_wrapper).to receive(:open)
92
- .with(expected_source_filepath, 'w')
94
+ .with(expected_source_filepath, 'wb')
93
95
  .and_yield(source_file_handle)
94
96
 
95
97
  # Spy on generate_header and generate_source -- allow them to be called but track the calls
@@ -145,8 +147,8 @@ describe GeneratorPartials do
145
147
  expect(@file_path_utils).to have_received(:form_partial_implementation_header_filename).with(name)
146
148
 
147
149
  # Verify file operations
148
- expect(@file_wrapper).to have_received(:open).with(expected_header_filepath, 'w')
149
- expect(@file_wrapper).to have_received(:open).with(expected_source_filepath, 'w')
150
+ expect(@file_wrapper).to have_received(:open).with(expected_header_filepath, 'wb')
151
+ expect(@file_wrapper).to have_received(:open).with(expected_source_filepath, 'wb')
150
152
 
151
153
  # Verify return value is the source filepath
152
154
  expect(result).to eq(expected_source_filepath)
@@ -167,9 +169,11 @@ describe GeneratorPartials do
167
169
  .and_return(header_filename)
168
170
 
169
171
  # Mock FileWrapper.open to yield a file handle
172
+ # Binary mode ('wb'): generated files carry embedded content whose line
173
+ # endings must reach disk unchanged, which text mode would not guarantee.
170
174
  file_handle = double('file_handle')
171
175
  allow(@file_wrapper).to receive(:open)
172
- .with(expected_filepath, 'w')
176
+ .with(expected_filepath, 'wb')
173
177
  .and_yield(file_handle)
174
178
 
175
179
  # Spy on generate_header -- allow it to be called but track the call
@@ -211,7 +215,7 @@ describe GeneratorPartials do
211
215
  expect(@file_path_utils).to have_received(:form_partial_interface_header_filename).with(name)
212
216
 
213
217
  # Verify file operations
214
- expect(@file_wrapper).to have_received(:open).with(expected_filepath, 'w')
218
+ expect(@file_wrapper).to have_received(:open).with(expected_filepath, 'wb')
215
219
 
216
220
  # Verify return value is the header filepath
217
221
  expect(result).to eq(expected_filepath)
@@ -649,6 +653,28 @@ describe GeneratorPartials do
649
653
  expect( buf.string.strip() ).to eq file_contents.strip()
650
654
  end
651
655
 
656
+ it "should pass a code_block's line endings through unchanged, including embedded CRLF" do
657
+ # code_block content is written to the IO handle exactly as given, byte for
658
+ # byte, since the file it ends up in is opened by its caller in binary mode
659
+ # specifically so nothing here alters a line ending already present in it.
660
+ code_block_with_crlf = "void helloBlanks(void) {\r\n\r\n return;\r\n}"
661
+
662
+ defn = Partials.manufacture_function_definition(
663
+ line_num: 2,
664
+ source_filepath: 'src/blanks.c',
665
+ name: 'helloBlanks',
666
+ signature: 'void helloBlanks(void)',
667
+ code_block: code_block_with_crlf
668
+ )
669
+ c_module = make_module(
670
+ CExtractorTypes::CFunctionDefinition.new(name: 'helloBlanks')
671
+ )
672
+
673
+ @generator.send(:generate_source, buf, [], [defn], c_module)
674
+
675
+ expect( buf.string ).to include( code_block_with_crlf )
676
+ end
677
+
652
678
  it "should emit only variable declarations and function definitions from all four element_sequence categories" do
653
679
  # Source generation emits two categories and silently skips two others:
654
680
  # CVariableDeclaration → emitted as-is