Skip to content

Commit 89d5a81

Browse files
committed
Refactor test runner for clarity
1 parent 6a6b9e4 commit 89d5a81

File tree

1 file changed

+110
-100
lines changed

1 file changed

+110
-100
lines changed

exe/arduino_ci.rb

+110-100
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
require 'optparse'
66

77
WIDTH = 80
8-
FIND_FILES_INDENT = 4
98
VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze
109
VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze
1110

@@ -99,7 +98,7 @@ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abor
9998
else
10099
print line
101100
end
102-
STDOUT.flush
101+
$stdout.flush
103102
result = yield
104103
mark = mark_fn.nil? ? "" : mark_fn.call(result)
105104
# if multline, put checkmark at full width
@@ -125,7 +124,7 @@ def attempt_multiline(message, &block)
125124
end
126125

127126
# Make a nice status for something that kills the script immediately on failure
128-
FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze
127+
FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze
129128
def assure(message, &block)
130129
perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
131130
end
@@ -145,9 +144,7 @@ def inform_multiline(message, &block)
145144
# Assure that a platform exists and return its definition
146145
def assured_platform(purpose, name, config)
147146
platform_definition = config.platform_definition(name)
148-
assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do
149-
!platform_definition.nil?
150-
end
147+
assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? }
151148
platform_definition
152149
end
153150

@@ -170,17 +167,15 @@ def display_files(pathname)
170167
non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
171168

172169
# print files with an indent
173-
margin = " " * FIND_FILES_INDENT
174-
non_hidden.each { |p| puts "#{margin}#{p}" }
170+
puts " Files (excluding hidden files): #{non_hidden.size}"
171+
non_hidden.each { |p| puts " #{p}" }
175172
end
176173

177174
# @return [Array<String>] The list of installed libraries
178175
def install_arduino_library_dependencies(library_names, on_behalf_of, already_installed = [])
179176
installed = already_installed.clone
180-
library_names.map { |n| @backend.library_of_name(n) }.each do |l|
181-
if installed.include?(l)
182-
# do nothing
183-
elsif l.installed?
177+
(library_names.map { |n| @backend.library_of_name(n) } - installed).each do |l|
178+
if l.installed?
184179
inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
185180
else
186181
assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
@@ -195,30 +190,77 @@ def install_arduino_library_dependencies(library_names, on_behalf_of, already_in
195190
installed
196191
end
197192

198-
def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir)
193+
# @param example_platform_info [Hash] mapping of platform name to package information
194+
# @param board_package_url [Hash] mapping of package name to URL
195+
def install_all_packages(example_platform_info, board_package_url)
196+
# with all platform info, we can extract unique packages and their urls
197+
# do that, set the URLs, and download the packages
198+
all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
199+
200+
# make sure any non-builtin package has a URL defined
201+
all_packages.each { |p| assure("Board package #{p} has a defined URL") { board_package_url[p] } }
202+
203+
# set up all the board manager URLs.
204+
# we can safely reject nils now, they would be for the builtins
205+
all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
206+
unless all_urls.empty?
207+
assure_multiline("Setting board manager URLs") do
208+
@backend.board_manager_urls = all_urls
209+
result = @backend.board_manager_urls
210+
result.each { |u| puts " #{u}" }
211+
(all_urls - result).empty? # check that all_urls is completely contained in the result
212+
end
213+
end
214+
all_packages.each { |p| assure("Installing board package #{p}") { @backend.install_boards(p) } }
215+
end
216+
217+
# @param expectation_envvar [String] the name of the env var to check
218+
# @param operation [String] a description of what operation we might be skipping
219+
# @param filegroup_name [String] a description of the set of files without which we effectively skip the operation
220+
# @param dir_description [String] a description of the directory where we looked for the files
221+
# @param dir [Pathname] the directory where we looked for the files
222+
def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir_path)
223+
# alert future me about running the script from the wrong directory, instead of doing the huge file dump
224+
# otherwise, assume that the user might be running the script on a library with no actual unit tests
225+
if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
226+
inform_multiline("arduino_ci seems to be trying to test itself") do
227+
[
228+
"arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
229+
"the core library isn't really a valid thing to do... but it's easy for a developer (including the",
230+
"owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
231+
"the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
232+
].each { |l| puts " #{l}" }
233+
false
234+
end
235+
exit(1)
236+
end
237+
238+
# either the directory is empty, or it doesn't exist at all. message accordingly.
239+
(problem, dir_desc, dir) = if dir_path.exist?
240+
["No #{filegroup_name} were found in", dir_description, dir_path]
241+
else
242+
["No #{dir_description} at", "base directory", dir_path.parent]
243+
end
244+
245+
inform(problem) { dir_path }
246+
inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" }
199247
if ENV[expectation_envvar].nil?
200-
inform_multiline("Skipping #{operation}; no #{filegroup_name} were found in #{dir}") do
201-
puts " In case that's an error, this is what was found in the #{dir_description}:"
248+
inform_multiline("Skipping #{operation}") do
249+
puts " In case that's an error, this is what was found in the #{dir_desc}:"
202250
display_files(dir)
203-
puts "To force an error in this case, set the environment variable #{expectation_envvar}"
251+
puts " To force an error in this case, set the environment variable #{expectation_envvar}"
204252
true
205253
end
206254
else
207-
assure_multiline("No #{filegroup_name} were found in #{dir} and #{expectation_envvar} was set") do
208-
puts " This is what was found in the #{dir_description}:"
255+
assure_multiline("Dumping project's #{dir_desc} before exit") do
209256
display_files(dir)
210257
false
211258
end
212259
end
213260
end
214261

215-
def perform_unit_tests(cpp_library, file_config)
216-
if @cli_options[:skip_unittests]
217-
inform("Skipping unit tests") { "as requested via command line" }
218-
return
219-
end
220-
config = file_config.with_override_config(@cli_options[:ci_config])
221-
262+
# report and return the set of compilers
263+
def get_annotated_compilers(config, cpp_library)
222264
# check GCC
223265
compilers = config.compilers_to_use
224266
assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
@@ -232,62 +274,54 @@ def perform_unit_tests(cpp_library, file_config)
232274
end
233275
inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
234276
end
277+
compilers
278+
end
235279

236-
# Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
237-
all_platform_info = {}
238-
config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) }
280+
def perform_unit_tests(cpp_library, file_config)
281+
if @cli_options[:skip_unittests]
282+
inform("Skipping unit tests") { "as requested via command line" }
283+
return
284+
end
285+
286+
config = file_config.with_override_config(@cli_options[:ci_config])
287+
compilers = get_annotated_compilers(config, cpp_library)
288+
config.platforms_to_unittest.each_with_object({}) { |p, acc| acc[p] = assured_platform("unittest", p, config) }
239289

240290
inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
241291

242-
# iterate boards / tests
243-
if !cpp_library.tests_dir.exist?
244-
# alert future me about running the script from the wrong directory, instead of doing the huge file dump
245-
# otherwise, assume that the user might be running the script on a library with no actual unit tests
246-
if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
247-
inform_multiline("arduino_ci seems to be trying to test itself") do
248-
[
249-
"arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
250-
"the core library isn't really a valid thing to do... but it's easy for a developer (including the",
251-
"owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
252-
"the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
253-
].each { |l| puts " #{l}" }
254-
false
255-
end
256-
exit(1)
257-
else
258-
inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
259-
puts " In case that's an error, this is what was found in the library:"
260-
display_files(cpp_library.tests_dir.parent)
261-
true
262-
end
263-
end
264-
elsif cpp_library.test_files.empty?
292+
# Handle lack of test files
293+
if cpp_library.test_files.empty?
265294
handle_expectation_of_files(VAR_EXPECT_UNITTESTS, "unit tests", "test files", "tests directory", cpp_library.tests_dir)
266-
elsif config.platforms_to_unittest.empty?
295+
return
296+
end
297+
298+
# Handle lack of platforms
299+
if config.platforms_to_unittest.empty?
267300
inform("Skipping unit tests") { "no platforms were requested" }
268-
else
269-
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
270-
271-
config.platforms_to_unittest.each do |p|
272-
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
273-
unittest_name = unittest_path.basename.to_s
274-
compilers.each do |gcc_binary|
275-
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
276-
exe = cpp_library.build_for_test_with_configuration(
277-
unittest_path,
278-
config.aux_libraries_for_unittest,
279-
gcc_binary,
280-
config.gcc_config(p)
281-
)
282-
puts
283-
unless exe
284-
puts "Last command: #{cpp_library.last_cmd}"
285-
puts cpp_library.last_out
286-
puts cpp_library.last_err
287-
next false
288-
end
289-
cpp_library.run_test_file(exe)
301+
return
302+
end
303+
304+
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
305+
306+
config.platforms_to_unittest.each do |p|
307+
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
308+
unittest_name = unittest_path.basename.to_s
309+
compilers.each do |gcc_binary|
310+
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
311+
exe = cpp_library.build_for_test_with_configuration(
312+
unittest_path,
313+
config.aux_libraries_for_unittest,
314+
gcc_binary,
315+
config.gcc_config(p)
316+
)
317+
puts
318+
unless exe
319+
puts "Last command: #{cpp_library.last_cmd}"
320+
puts cpp_library.last_out
321+
puts cpp_library.last_err
322+
next false
290323
end
324+
cpp_library.run_test_file(exe)
291325
end
292326
end
293327
end
@@ -325,38 +359,14 @@ def perform_example_compilation_tests(cpp_library, config)
325359
aux_libraries.merge(ovr_config.aux_libraries_for_build)
326360
end
327361

328-
# with all platform info, we can extract unique packages and their urls
329-
# do that, set the URLs, and download the packages
330-
all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
331-
332-
# make sure any non-builtin package has a URL defined
333-
all_packages.each do |p|
334-
assure("Board package #{p} has a defined URL") { board_package_url[p] }
335-
end
336-
337-
# set up all the board manager URLs.
338-
# we can safely reject nils now, they would be for the builtins
339-
all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
340-
341-
unless all_urls.empty?
342-
assure("Setting board manager URLs") do
343-
@backend.board_manager_urls = all_urls
344-
end
345-
end
346-
347-
all_packages.each do |p|
348-
assure("Installing board package #{p}") do
349-
@backend.install_boards(p)
350-
end
351-
end
352-
362+
install_all_packages(example_platform_info, board_package_url)
353363
install_arduino_library_dependencies(aux_libraries, "<compile/libraries>")
354364

355365
if config.platforms_to_build.empty?
356366
inform("Skipping builds") { "no platforms were requested" }
357367
return
358368
elsif library_examples.empty?
359-
handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the library directory", installed_library_path)
369+
handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the examples directory", cpp_library.examples_dir)
360370
return
361371
end
362372

0 commit comments

Comments
 (0)