Skip to content

Commit d1f5c6f

Browse files
committed
Merge branch 'feature/preprocessing_plugin_hooks_and_documentation' into test/ceedling_0_32_rc
2 parents 6113249 + 3701a8e commit d1f5c6f

19 files changed

+805
-367
lines changed

docs/CeedlingPacket.md

+400-234
Large diffs are not rendered by default.

docs/ReleaseNotes.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ This Ceedling release is probably the most significant since the project was fir
1212

1313
Ceedling now runs in Ruby 3. Builds can now run much faster than previous versions because of parallelized tasks. For test suites, header file search paths, code defines, and tool run flags are now customizable per test executable.
1414

15-
🏴‍☠️ **_Ahoy!_** There be **[breaking changes](#-Breaking-Changes)** ahead, mateys! Arrr…
15+
### Avast, Breaking Changes, Ye Scallywags! 🏴‍☠️
16+
17+
**_Ahoy!_** There be **[breaking changes](#-Breaking-Changes)** ahead, mateys! Arrr…
1618

1719
### Big Deal Highlights 🏅
1820

@@ -40,6 +42,15 @@ The following new features (discussed in later sections) contribute to this new
4042
- `[:defines]` handling. `#define`s are now specified for the compilation of all modules comprising a test executable. Matching is only against test file names but now includes wildcard and regular expression options.
4143
- `[:flags]` handling. Flags (e.g. `-std=c99`) are now specified for the build steps—preprocessing, compilation, and linking—of all modules comprising a test executable. Matching is only against test file names and now includes more sensible and robust wildcard and regular expression options.
4244

45+
### Important Changes in Behavior to Be Aware Of 🚨
46+
47+
- **Test suite build order 🔢.** Ceedling no longer builds each test executable one at a time. From the tasks you provide at the command line, Ceedling now collects up and batches all preprocessing steps, all mock generation, all test runner generation, all compilation, etc. Previously you would see each of these done for a single test executable and then repeated for the next executable and so on. Now, each build step happens to completion for all specified tests before moving on to the next build step.
48+
- **Logging output order 🔢.** When multi-threaded builds are enabled, logging output may not be what you expect. Progress statements may be all batched together or interleaved in ways that are misleading. The steps are happening in the correct order. How you are informed of them may be somewhat out of order.
49+
- **Files generated multiple times 🔀.** Now that each test is essentially a self-contained mini-project, some output may be generated multiple times. For instance, if the same mock is required by multiple tests, it will be generated multiple times. The same holds for compilation of source files into object files. A coming version of Ceedling will concentrate on optimizations to reuse any output that is truly identical across tests.
50+
- **Test suite plugin runs 🏃🏻.** Because build steps are run to completion across all the tests you specify at the command line (e.g. all the mocks for your tests are generated at one time) you may need to adjust how you depend on build steps.
51+
52+
Together, these changes may cause you to think that Ceedling is running steps out of order or duplicating work. While bugs are always possible, more than likely, the output you see and the build ordering is expected.
53+
4354
### Medium Deal Highlights 🥈
4455

4556
#### `TEST_SOURCE_FILE(...)`

lib/ceedling/configurator.rb

+17-3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ def reset_defaults(config)
6969
end
7070

7171

72+
# Set up essential flattened config
73+
# (In case YAML validation failure prevents flattening of config into configurator accessors)
74+
def set_debug(config)
75+
if config[:project][:debug]
76+
eval("def project_debug() return true end", binding())
77+
eval("def project_verbosity() return Verbosity::DEBUG end", binding())
78+
end
79+
end
80+
81+
7282
# The default values defined in defaults.rb (eg. DEFAULT_TOOLS_TEST) are populated
7383
# into @param config
7484
def populate_defaults(config)
@@ -144,13 +154,17 @@ def get_cmock_config
144154
end
145155

146156

147-
# grab tool names from yaml and insert into tool structures so available for error messages
148-
# set up default values
157+
# Grab tool names from yaml and insert into tool structures so available for error messages.
158+
# Set up default values.
149159
def tools_setup(config)
150160
config[:tools].each_key do |name|
151161
tool = config[:tools][name]
152162

153-
# populate name if not given
163+
if not tool.is_a?(Hash)
164+
raise CeedlingException.new("ERROR: Expected configuration for tool :#{name} is a Hash but found #{tool.class}")
165+
end
166+
167+
# Populate name if not given
154168
tool[:name] = name.to_s if (tool[:name].nil?)
155169

156170
# handle inline ruby string substitution in executable

lib/ceedling/defaults.rb

+1
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@
306306
:options_paths => [],
307307
:release_build => false,
308308
:use_backtrace_gdb_reporter => false,
309+
:debug => false
309310
},
310311

311312
:release_build => {

lib/ceedling/include_pathinator.rb

+11
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,15 @@ def lookup_test_directive_include_paths(filepath)
4949
return @extractor.lookup_include_paths_list(filepath)
5050
end
5151

52+
# Gather together [:paths][:test] that actually contain .h files
53+
def collect_test_include_paths
54+
paths = []
55+
@configurator.collection_paths_test.each do |path|
56+
headers = @file_wrapper.directory_listing( File.join( path, '*' + @configurator.extension_header ) )
57+
paths << path if headers.length > 0
58+
end
59+
60+
return paths
61+
end
62+
5263
end

lib/ceedling/objects.yml

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ preprocessinator:
275275
- file_path_utils
276276
- file_wrapper
277277
- yaml_wrapper
278+
- plugin_manager
278279
- project_config_manager
279280
- configurator
280281
- test_context_extractor

lib/ceedling/plugin.rb

+16-8
Original file line numberDiff line numberDiff line change
@@ -43,35 +43,43 @@ def initialize(system_objects, name)
4343

4444
def setup; end
4545

46-
# mock generation
46+
# Preprocessing (before / after each and every header file preprocessing operation before mocking)
47+
def pre_mock_preprocess(arg_hash); end
48+
def post_mock_preprocess(arg_hash); end
49+
50+
# Preprocessing (before / after each and every test preprocessing operation before runner generation)
51+
def pre_test_preprocess(arg_hash); end
52+
def post_test_preprocess(arg_hash); end
53+
54+
# Mock generation (before / after each and every mock)
4755
def pre_mock_generate(arg_hash); end
4856
def post_mock_generate(arg_hash); end
4957

50-
# test runner generation
58+
# Test runner generation (before / after each and every test runner)
5159
def pre_runner_generate(arg_hash); end
5260
def post_runner_generate(arg_hash); end
5361

54-
# compilation (test or source)
62+
# Compilation (before / after each and test or source file compilation)
5563
def pre_compile_execute(arg_hash); end
5664
def post_compile_execute(arg_hash); end
5765

58-
# linking (test or source)
66+
# Linking (before / after each and every test executable or release artifact)
5967
def pre_link_execute(arg_hash); end
6068
def post_link_execute(arg_hash); end
6169

62-
# test fixture execution
70+
# Test fixture execution (before / after each and every test fixture executable)
6371
def pre_test_fixture_execute(arg_hash); end
6472
def post_test_fixture_execute(arg_hash); end
6573

66-
# test task
74+
# Test task (before / after each test executable build)
6775
def pre_test(test); end
6876
def post_test(test); end
6977

70-
# release task
78+
# Release task (before / after a release build)
7179
def pre_release; end
7280
def post_release; end
7381

74-
# whole shebang (any use of Ceedling)
82+
# Whole shebang (any use of Ceedling)
7583
def pre_build; end
7684
def post_build; end
7785

lib/ceedling/plugin_manager.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ def register_build_failure(message)
5656

5757
#### execute all plugin methods ####
5858

59+
def pre_mock_preprocess(arg_hash); execute_plugins(:pre_mock_preprocess, arg_hash); end
60+
def post_mock_preprocess(arg_hash); execute_plugins(:post_mock_preprocess, arg_hash); end
61+
62+
def pre_test_preprocess(arg_hash); execute_plugins(:pre_test_preprocess, arg_hash); end
63+
def post_test_preprocess(arg_hash); execute_plugins(:post_test_preprocess, arg_hash); end
64+
5965
def pre_mock_generate(arg_hash); execute_plugins(:pre_mock_generate, arg_hash); end
6066
def post_mock_generate(arg_hash); execute_plugins(:post_mock_generate, arg_hash); end
6167

@@ -98,7 +104,7 @@ def execute_plugins(method, *args)
98104
begin
99105
plugin.send(method, *args) if plugin.respond_to?(method)
100106
rescue
101-
puts "Exception raised in plugin: #{plugin.name}, in method #{method}"
107+
@streaminator.stderr_puts("Exception raised in plugin: #{plugin.name}, in method #{method}")
102108
raise
103109
end
104110
end

lib/ceedling/preprocessinator.rb

+81-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Preprocessinator
88
:file_path_utils,
99
:file_wrapper,
1010
:yaml_wrapper,
11+
:plugin_manager,
1112
:project_config_manager,
1213
:configurator,
1314
:test_context_extractor,
@@ -37,12 +38,15 @@ def extract_testing_context(filepath:, test:, flags:, include_paths:, defines:)
3738
@test_context_extractor.collect_includes( filepath )
3839
else
3940
# Run test file through preprocessor to parse out include statements and then collect header files, mocks, etc.
40-
includes = preprocess_includes(
41+
arg_hash = {
4142
filepath: filepath,
4243
test: test,
4344
flags: flags,
4445
include_paths: include_paths,
45-
defines: defines)
46+
defines: defines
47+
}
48+
49+
includes = preprocess_includes(**arg_hash)
4650

4751
msg = @reportinator.generate_progress( "Processing #include statements for #{File.basename(filepath)}" )
4852
@streaminator.stdout_puts( msg, Verbosity::NORMAL )
@@ -51,46 +55,92 @@ def extract_testing_context(filepath:, test:, flags:, include_paths:, defines:)
5155
end
5256
end
5357

54-
def preprocess_header_file(filepath:, test:, flags:, include_paths:, defines:)
55-
# Extract shallow includes & print status message
56-
includes = preprocess_file_common(
57-
filepath: filepath,
58-
test: test,
59-
flags: flags,
60-
include_paths: include_paths,
61-
defines: defines
62-
)
58+
def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, defines:)
59+
preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, test )
60+
61+
plugin_arg_hash = {
62+
header_file: filepath,
63+
preprocessed_header_file: preprocessed_filepath,
64+
test: test,
65+
flags: flags,
66+
include_paths: include_paths,
67+
defines: defines
68+
}
69+
70+
# Trigger pre_mock_preprocessing plugin hook
71+
@plugin_manager.pre_mock_preprocess( plugin_arg_hash )
72+
73+
arg_hash = {
74+
filepath: filepath,
75+
test: test,
76+
flags: flags,
77+
include_paths: include_paths,
78+
defines: defines
79+
}
80+
81+
# Extract shallow includes & print status message
82+
includes = preprocess_file_common(**arg_hash)
83+
84+
arg_hash = {
85+
source_filepath: filepath,
86+
preprocessed_filepath: preprocessed_filepath,
87+
includes: includes,
88+
flags: flags,
89+
include_paths: include_paths,
90+
defines: defines
91+
}
6392

6493
# Run file through preprocessor & further process result
65-
return @file_handler.preprocess_header_file(
66-
filepath: filepath,
67-
subdir: test,
68-
includes: includes,
69-
flags: flags,
70-
include_paths: include_paths,
71-
defines: defines
72-
)
94+
@file_handler.preprocess_header_file(**arg_hash)
95+
96+
# Trigger post_mock_preprocessing plugin hook
97+
@plugin_manager.post_mock_preprocess( plugin_arg_hash )
98+
99+
return preprocessed_filepath
73100
end
74101

75102
def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
76-
# Extract shallow includes & print status message
77-
includes = preprocess_file_common(
103+
preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, test )
104+
105+
plugin_arg_hash = {
106+
test_file: filepath,
107+
preprocessed_test_file: preprocessed_filepath,
108+
test: test,
109+
flags: flags,
110+
include_paths: include_paths,
111+
defines: defines
112+
}
113+
114+
# Trigger pre_mock_preprocessing plugin hook
115+
@plugin_manager.pre_test_preprocess( plugin_arg_hash )
116+
117+
arg_hash = {
78118
filepath: filepath,
79119
test: test,
80120
flags: flags,
81121
include_paths: include_paths,
82-
defines: defines
83-
)
122+
defines: defines
123+
}
124+
125+
# Extract shallow includes & print status message
126+
includes = preprocess_file_common(**arg_hash)
127+
128+
arg_hash = {
129+
source_filepath: filepath,
130+
preprocessed_filepath: preprocessed_filepath,
131+
includes: includes,
132+
flags: flags,
133+
include_paths: include_paths,
134+
defines: defines
135+
}
84136

85137
# Run file through preprocessor & further process result
86-
return @file_handler.preprocess_test_file(
87-
filepath: filepath,
88-
subdir: test,
89-
includes: includes,
90-
flags: flags,
91-
include_paths: include_paths,
92-
defines: defines
93-
)
138+
@file_handler.preprocess_test_file(**arg_hash)
139+
140+
# Trigger pre_mock_preprocessing plugin hook
141+
@plugin_manager.post_test_preprocess( plugin_arg_hash )
142+
143+
return preprocessed_filepath
94144
end
95145

96146
def preprocess_file_directives(filepath)

lib/ceedling/preprocessinator_file_handler.rb

+5-13
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ class PreprocessinatorFileHandler
33

44
constructor :preprocessinator_extractor, :configurator, :flaginator, :tool_executor, :file_path_utils, :file_wrapper, :streaminator
55

6-
def preprocess_header_file(filepath:, subdir:, includes:, flags:, include_paths:, defines:)
7-
preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, subdir )
8-
9-
filename = File.basename(filepath)
6+
def preprocess_header_file(source_filepath:, preprocessed_filepath:, includes:, flags:, include_paths:, defines:)
7+
filename = File.basename(source_filepath)
108

119
command = @tool_executor.build_command_line(
1210
@configurator.tools_test_file_preprocessor,
1311
flags,
14-
filepath,
12+
source_filepath,
1513
preprocessed_filepath,
1614
defines,
1715
include_paths
@@ -58,17 +56,13 @@ def preprocess_header_file(filepath:, subdir:, includes:, flags:, include_paths:
5856
contents.gsub!( /(\h*\n){3,}/, "\n\n" )
5957

6058
@file_wrapper.write( preprocessed_filepath, contents )
61-
62-
return preprocessed_filepath
6359
end
6460

65-
def preprocess_test_file(filepath:, subdir:, includes:, flags:, include_paths:, defines:)
66-
preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, subdir )
67-
61+
def preprocess_test_file(source_filepath:, preprocessed_filepath:, includes:, flags:, include_paths:, defines:)
6862
command = @tool_executor.build_command_line(
6963
@configurator.tools_test_file_preprocessor,
7064
flags,
71-
filepath,
65+
source_filepath,
7266
preprocessed_filepath,
7367
defines,
7468
include_paths
@@ -102,8 +96,6 @@ def preprocess_test_file(filepath:, subdir:, includes:, flags:, include_paths:,
10296
contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Collapse repeated blank lines
10397

10498
@file_wrapper.write( preprocessed_filepath, contents )
105-
106-
return preprocessed_filepath
10799
end
108100

109101

lib/ceedling/rakefile.rb

+7-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
@ceedling[:setupinator].do_setup( project_config )
4949

5050
# Configure Ruby's default reporting for Thread exceptions.
51-
unless @ceedling[:configurator].project_verbosity == Verbosity::DEBUG
51+
unless @ceedling[:configurator].project_debug
5252
# In Ceedling's case thread scenarios will fall into these buckets:
5353
# 1. Jobs shut down cleanly
5454
# 2. Jobs shut down at garbage collected after a build step terminates with an error
@@ -71,7 +71,12 @@
7171
# load rakefile component files (*.rake)
7272
PROJECT_RAKEFILE_COMPONENT_FILES.each { |component| load(component) }
7373
rescue StandardError => e
74-
$stderr.puts(e)
74+
$stderr.puts("#{e.class} ==> #{e.message}")
75+
if @ceedling[:configurator].project_debug
76+
$stderr.puts("Backtrace ==>")
77+
$stderr.puts(e.backtrace)
78+
end
79+
7580
abort # Rake's abort
7681
end
7782

lib/ceedling/setupinator.rb

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def do_setup(config_hash)
2020
# load up all the constants and accessors our rake files, objects, & external scripts will need;
2121
# note: configurator modifies the cmock section of the hash with a couple defaults to tie
2222
# project together - the modified hash is used to build cmock object
23+
@ceedling[:configurator].set_debug( config_hash )
2324
@ceedling[:configurator].populate_defaults( config_hash )
2425
@ceedling[:configurator].populate_unity_defaults( config_hash )
2526
@ceedling[:configurator].populate_cmock_defaults( config_hash )

0 commit comments

Comments
 (0)