5
5
require 'optparse'
6
6
7
7
WIDTH = 80
8
- FIND_FILES_INDENT = 4
8
+ VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES" . freeze
9
+ VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS" . freeze
9
10
10
11
@failure_count = 0
11
12
@passfail = proc { |result | result ? "✓" : "✗" }
@@ -48,6 +49,10 @@ def self.parse(options)
48
49
49
50
opts . on ( "-h" , "--help" , "Prints this help" ) do
50
51
puts opts
52
+ puts
53
+ puts "Additionally, the following environment variables control the script:"
54
+ puts " - #{ VAR_EXPECT_EXAMPLES } - if set, testing will fail if no example sketches are present"
55
+ puts " - #{ VAR_EXPECT_UNITTESTS } - if set, testing will fail if no unit tests are present"
51
56
exit
52
57
end
53
58
end
@@ -93,7 +98,7 @@ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abor
93
98
else
94
99
print line
95
100
end
96
- STDOUT . flush
101
+ $stdout . flush
97
102
result = yield
98
103
mark = mark_fn . nil? ? "" : mark_fn . call ( result )
99
104
# if multline, put checkmark at full width
@@ -119,7 +124,7 @@ def attempt_multiline(message, &block)
119
124
end
120
125
121
126
# Make a nice status for something that kills the script immediately on failure
122
- 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
123
128
def assure ( message , &block )
124
129
perform_action ( message , false , @passfail , FAILED_ASSURANCE_MESSAGE , true , true , &block )
125
130
end
@@ -139,9 +144,7 @@ def inform_multiline(message, &block)
139
144
# Assure that a platform exists and return its definition
140
145
def assured_platform ( purpose , name , config )
141
146
platform_definition = config . platform_definition ( name )
142
- assure ( "Requested #{ purpose } platform '#{ name } ' is defined in 'platforms' YML" ) do
143
- !platform_definition . nil?
144
- end
147
+ assure ( "Requested #{ purpose } platform '#{ name } ' is defined in 'platforms' YML" ) { !platform_definition . nil? }
145
148
platform_definition
146
149
end
147
150
@@ -157,24 +160,22 @@ def file_is_hidden_somewhere?(path)
157
160
# print out some files
158
161
def display_files ( pathname )
159
162
# `find` doesn't follow symlinks, so we should instead
160
- realpath = Host . symlink? ( pathname ) ? Host . readlink ( pathname ) : pathname
163
+ realpath = ArduinoCI :: Host . symlink? ( pathname ) ? ArduinoCI :: Host . readlink ( pathname ) : pathname
161
164
162
165
# suppress directories and dotfile-based things
163
166
all_files = realpath . find . select ( &:file? )
164
167
non_hidden = all_files . reject { |path | file_is_hidden_somewhere? ( path ) }
165
168
166
169
# print files with an indent
167
- margin = " " * FIND_FILES_INDENT
168
- non_hidden . each { |p | puts "#{ margin } #{ p } " }
170
+ puts " Files (excluding hidden files): #{ non_hidden . size } "
171
+ non_hidden . each { |p | puts " #{ p } " }
169
172
end
170
173
171
174
# @return [Array<String>] The list of installed libraries
172
175
def install_arduino_library_dependencies ( library_names , on_behalf_of , already_installed = [ ] )
173
176
installed = already_installed . clone
174
- library_names . map { |n | @backend . library_of_name ( n ) } . each do |l |
175
- if installed . include? ( l )
176
- # do nothing
177
- elsif l . installed?
177
+ ( library_names . map { |n | @backend . library_of_name ( n ) } - installed ) . each do |l |
178
+ if l . installed?
178
179
inform ( "Using pre-existing dependency of #{ on_behalf_of } " ) { l . name }
179
180
else
180
181
assure ( "Installing dependency of #{ on_behalf_of } : '#{ l . name } '" ) do
@@ -189,13 +190,77 @@ def install_arduino_library_dependencies(library_names, on_behalf_of, already_in
189
190
installed
190
191
end
191
192
192
- def perform_unit_tests ( cpp_library , file_config )
193
- if @cli_options [ :skip_unittests ]
194
- inform ( "Skipping unit tests" ) { "as requested via command line" }
195
- return
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
196
213
end
197
- config = file_config . with_override_config ( @cli_options [ :ci_config ] )
214
+ all_packages . each { |p | assure ( "Installing board package #{ p } " ) { @backend . install_boards ( p ) } }
215
+ end
198
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 ] } " }
247
+ if ENV [ expectation_envvar ] . nil?
248
+ inform_multiline ( "Skipping #{ operation } " ) do
249
+ puts " In case that's an error, this is what was found in the #{ dir_desc } :"
250
+ display_files ( dir )
251
+ puts " To force an error in this case, set the environment variable #{ expectation_envvar } "
252
+ true
253
+ end
254
+ else
255
+ assure_multiline ( "Dumping project's #{ dir_desc } before exit" ) do
256
+ display_files ( dir )
257
+ false
258
+ end
259
+ end
260
+ end
261
+
262
+ # report and return the set of compilers
263
+ def get_annotated_compilers ( config , cpp_library )
199
264
# check GCC
200
265
compilers = config . compilers_to_use
201
266
assure ( "The set of compilers (#{ compilers . length } ) isn't empty" ) { !compilers . empty? }
@@ -209,66 +274,54 @@ def perform_unit_tests(cpp_library, file_config)
209
274
end
210
275
inform ( "libasan availability for #{ gcc_binary } " ) { cpp_library . libasan? ( gcc_binary ) }
211
276
end
277
+ compilers
278
+ end
279
+
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
212
285
213
- # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
214
- all_platform_info = { }
215
- config . platforms_to_unittest . each { |p | all_platform_info [ p ] = assured_platform ( "unittest" , p , config ) }
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 ) }
216
289
217
290
inform ( "Library conforms to Arduino library specification" ) { cpp_library . one_point_five? ? "1.5" : "1.0" }
218
291
219
- # iterate boards / tests
220
- if !cpp_library . tests_dir . exist?
221
- # alert future me about running the script from the wrong directory, instead of doing the huge file dump
222
- # otherwise, assume that the user might be running the script on a library with no actual unit tests
223
- if Pathname . new ( __dir__ ) . parent == Pathname . new ( Dir . pwd )
224
- inform_multiline ( "arduino_ci seems to be trying to test itself" ) do
225
- [
226
- "arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against" ,
227
- "the core library isn't really a valid thing to do... but it's easy for a developer (including the" ,
228
- "owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of" ,
229
- "the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
230
- ] . each { |l | puts " #{ l } " }
231
- false
232
- end
233
- exit ( 1 )
234
- else
235
- inform_multiline ( "Skipping unit tests; no tests dir at #{ cpp_library . tests_dir } " ) do
236
- puts " In case that's an error, this is what was found in the library:"
237
- display_files ( cpp_library . tests_dir . parent )
238
- true
239
- end
240
- end
241
- elsif cpp_library . test_files . empty?
242
- inform_multiline ( "Skipping unit tests; no test files were found in #{ cpp_library . tests_dir } " ) do
243
- puts " In case that's an error, this is what was found in the tests directory:"
244
- display_files ( cpp_library . tests_dir )
245
- true
246
- end
247
- elsif config . platforms_to_unittest . empty?
292
+ # Handle lack of test files
293
+ if cpp_library . test_files . empty?
294
+ handle_expectation_of_files ( VAR_EXPECT_UNITTESTS , "unit tests" , "test files" , "tests directory" , cpp_library . tests_dir )
295
+ return
296
+ end
297
+
298
+ # Handle lack of platforms
299
+ if config . platforms_to_unittest . empty?
248
300
inform ( "Skipping unit tests" ) { "no platforms were requested" }
249
- else
250
- install_arduino_library_dependencies ( config . aux_libraries_for_unittest , "<unittest/libraries>" )
251
-
252
- config . platforms_to_unittest . each do | p |
253
- config . allowable_unittest_files ( cpp_library . test_files ) . each do | unittest_path |
254
- unittest_name = unittest_path . basename . to_s
255
- compilers . each do |gcc_binary |
256
- attempt_multiline ( "Unit testing #{ unittest_name } with #{ gcc_binary } for #{ p } " ) do
257
- exe = cpp_library . build_for_test_with_configuration (
258
- unittest_path ,
259
- config . aux_libraries_for_unittest ,
260
- gcc_binary ,
261
- config . gcc_config ( p )
262
- )
263
- puts
264
- unless exe
265
- puts "Last command: #{ cpp_library . last_cmd } "
266
- puts cpp_library . last_out
267
- puts cpp_library . last_err
268
- next false
269
- end
270
- 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
271
323
end
324
+ cpp_library . run_test_file ( exe )
272
325
end
273
326
end
274
327
end
@@ -306,40 +359,14 @@ def perform_example_compilation_tests(cpp_library, config)
306
359
aux_libraries . merge ( ovr_config . aux_libraries_for_build )
307
360
end
308
361
309
- # with all platform info, we can extract unique packages and their urls
310
- # do that, set the URLs, and download the packages
311
- all_packages = example_platform_info . values . map { |v | v [ :package ] } . uniq . reject ( &:nil? )
312
-
313
- # make sure any non-builtin package has a URL defined
314
- all_packages . each do |p |
315
- assure ( "Board package #{ p } has a defined URL" ) { board_package_url [ p ] }
316
- end
317
-
318
- # set up all the board manager URLs.
319
- # we can safely reject nils now, they would be for the builtins
320
- all_urls = all_packages . map { |p | board_package_url [ p ] } . uniq . reject ( &:nil? )
321
-
322
- unless all_urls . empty?
323
- assure ( "Setting board manager URLs" ) do
324
- @backend . board_manager_urls = all_urls
325
- end
326
- end
327
-
328
- all_packages . each do |p |
329
- assure ( "Installing board package #{ p } " ) do
330
- @backend . install_boards ( p )
331
- end
332
- end
333
-
362
+ install_all_packages ( example_platform_info , board_package_url )
334
363
install_arduino_library_dependencies ( aux_libraries , "<compile/libraries>" )
335
364
336
365
if config . platforms_to_build . empty?
337
366
inform ( "Skipping builds" ) { "no platforms were requested" }
338
367
return
339
368
elsif library_examples . empty?
340
- inform_multiline ( "Skipping builds; no examples found in #{ installed_library_path } " ) do
341
- display_files ( installed_library_path )
342
- end
369
+ handle_expectation_of_files ( VAR_EXPECT_EXAMPLES , "builds" , "examples" , "the examples directory" , cpp_library . examples_dir )
343
370
return
344
371
end
345
372
0 commit comments