arduino_ci 0.3.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +125 -69
- data/REFERENCE.md +711 -0
- data/cpp/arduino/Arduino.h +1 -7
- data/cpp/arduino/ArduinoDefines.h +3 -0
- data/cpp/arduino/AvrMath.h +117 -17
- data/cpp/arduino/Client.h +27 -0
- data/cpp/arduino/EEPROM.h +64 -0
- data/cpp/arduino/Godmode.cpp +7 -0
- data/cpp/arduino/Godmode.h +121 -15
- data/cpp/arduino/HardwareSerial.h +4 -4
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/Print.h +9 -12
- data/cpp/arduino/Printable.h +8 -0
- data/cpp/arduino/SPI.h +11 -3
- data/cpp/arduino/Server.h +5 -0
- data/cpp/arduino/Udp.h +27 -0
- data/cpp/arduino/Wire.h +197 -77
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/unittest/ArduinoUnitTests.h +32 -0
- data/cpp/unittest/Assertion.h +54 -26
- data/cpp/unittest/Compare.h +58 -51
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +538 -0
- data/exe/arduino_ci_remote.rb +2 -393
- data/exe/arduino_library_location.rb +2 -2
- data/exe/ensure_arduino_installation.rb +7 -1
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_backend.rb +238 -0
- data/lib/arduino_ci/arduino_downloader.rb +43 -73
- data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
- data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
- data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
- data/lib/arduino_ci/arduino_installation.rb +18 -80
- data/lib/arduino_ci/ci_config.rb +8 -11
- data/lib/arduino_ci/cpp_library.rb +250 -59
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +101 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +57 -6
- metadata +19 -87
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
- data/lib/arduino_ci/arduino_cmd.rb +0 -332
- data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
- data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
data/exe/arduino_ci.rb
ADDED
@@ -0,0 +1,538 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'arduino_ci'
|
3
|
+
require 'set'
|
4
|
+
require 'pathname'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
WIDTH = 80
|
8
|
+
VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze
|
9
|
+
VAR_USE_SUBDIR = "USE_SUBDIR".freeze
|
10
|
+
VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze
|
11
|
+
VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze
|
12
|
+
|
13
|
+
@failure_count = 0
|
14
|
+
@passfail = proc { |result| result ? "✓" : "✗" }
|
15
|
+
@backend = nil
|
16
|
+
|
17
|
+
# Use some basic parsing to allow command-line overrides of config
|
18
|
+
class Parser
|
19
|
+
def self.parse(options)
|
20
|
+
unit_config = {}
|
21
|
+
output_options = {
|
22
|
+
skip_unittests: false,
|
23
|
+
skip_compilation: false,
|
24
|
+
ci_config: {
|
25
|
+
"unittest" => unit_config
|
26
|
+
},
|
27
|
+
}
|
28
|
+
|
29
|
+
opt_parser = OptionParser.new do |opts|
|
30
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
31
|
+
|
32
|
+
opts.on("--skip-unittests", "Don't run unit tests") do |p|
|
33
|
+
output_options[:skip_unittests] = p
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p|
|
37
|
+
output_options[:skip_compilation] = p
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
|
41
|
+
unit_config["testfiles"] ||= {}
|
42
|
+
unit_config["testfiles"]["select"] ||= []
|
43
|
+
unit_config["testfiles"]["select"] << p
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
|
47
|
+
unit_config["testfiles"] ||= {}
|
48
|
+
unit_config["testfiles"]["reject"] ||= []
|
49
|
+
unit_config["testfiles"]["reject"] << p
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-h", "--help", "Prints this help") do
|
53
|
+
puts opts
|
54
|
+
puts
|
55
|
+
puts "Additionally, the following environment variables control the script:"
|
56
|
+
puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory"
|
57
|
+
puts " prior to any automated library installation or testing (e.g. to install unoffical libraries)"
|
58
|
+
puts " - #{VAR_USE_SUBDIR} - if set, the script will install the library from this subdirectory of the cwd"
|
59
|
+
puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present"
|
60
|
+
puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present"
|
61
|
+
puts " - #{VAR_SKIP_LIBPROPS} - if set, testing will skip [experimental] library.properties validation"
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
opt_parser.parse!(options)
|
67
|
+
output_options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Read in command line options and make them read-only
|
72
|
+
@cli_options = (Parser.parse ARGV).freeze
|
73
|
+
|
74
|
+
# terminate after printing any debug info. TODO: capture debug info
|
75
|
+
def terminate(final = nil)
|
76
|
+
puts "Failures: #{@failure_count}"
|
77
|
+
unless @failure_count.zero? || final || @backend.nil?
|
78
|
+
puts "========== Last backend command (if relevant):"
|
79
|
+
puts @backend.last_msg.to_s
|
80
|
+
puts "========== Backend Stdout:"
|
81
|
+
puts @backend.last_out
|
82
|
+
puts "========== Backend Stderr:"
|
83
|
+
puts @backend.last_err
|
84
|
+
end
|
85
|
+
retcode = @failure_count.zero? ? 0 : 1
|
86
|
+
exit(retcode)
|
87
|
+
end
|
88
|
+
|
89
|
+
# make a nice status line for an action and react to the action
|
90
|
+
# TODO / note to self: inform_multline is tougher to write
|
91
|
+
# without altering the signature because it only leaves space
|
92
|
+
# for the checkmark _after_ the multiline, it doesn't know how
|
93
|
+
# to make that conditionally the body
|
94
|
+
# @param message String the text of the progress indicator
|
95
|
+
# @param multiline boolean whether multiline output is expected
|
96
|
+
# @param mark_fn block (string) -> string that says how to describe the result
|
97
|
+
# @param on_fail_msg String custom message for failure
|
98
|
+
# @param tally_on_fail boolean whether to increment @failure_count
|
99
|
+
# @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
|
100
|
+
def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
|
101
|
+
line = "#{message}... "
|
102
|
+
endline = "...#{message} "
|
103
|
+
if multiline
|
104
|
+
puts line
|
105
|
+
else
|
106
|
+
print line
|
107
|
+
end
|
108
|
+
$stdout.flush
|
109
|
+
result = yield
|
110
|
+
mark = mark_fn.nil? ? "" : mark_fn.call(result)
|
111
|
+
# if multline, put checkmark at full width
|
112
|
+
print endline if multiline
|
113
|
+
puts mark.to_s.rjust(WIDTH - line.length, " ")
|
114
|
+
unless result
|
115
|
+
puts on_fail_msg unless on_fail_msg.nil?
|
116
|
+
@failure_count += 1 if tally_on_fail
|
117
|
+
# print out error messaging here if we've captured it
|
118
|
+
terminate if abort_on_fail
|
119
|
+
end
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
# Make a nice status for something that defers any failure code until script exit
|
124
|
+
def attempt(message, &block)
|
125
|
+
perform_action(message, false, @passfail, nil, true, false, &block)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Make a nice status for something that defers any failure code until script exit
|
129
|
+
def attempt_multiline(message, &block)
|
130
|
+
perform_action(message, true, @passfail, nil, true, false, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Make a nice status for something that kills the script immediately on failure
|
134
|
+
FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze
|
135
|
+
def assure(message, &block)
|
136
|
+
perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
|
137
|
+
end
|
138
|
+
|
139
|
+
def assure_multiline(message, &block)
|
140
|
+
perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def inform(message, &block)
|
144
|
+
perform_action(message, false, proc { |x| x }, nil, false, false, &block)
|
145
|
+
end
|
146
|
+
|
147
|
+
def inform_multiline(message, &block)
|
148
|
+
perform_action(message, true, nil, nil, false, false, &block)
|
149
|
+
end
|
150
|
+
|
151
|
+
def rule(char)
|
152
|
+
puts char[0] * WIDTH
|
153
|
+
end
|
154
|
+
|
155
|
+
def warn(message)
|
156
|
+
inform("WARNING") { message }
|
157
|
+
end
|
158
|
+
|
159
|
+
def phase(name)
|
160
|
+
puts
|
161
|
+
rule("=")
|
162
|
+
inform("Beginning the next phase of testing") { name }
|
163
|
+
end
|
164
|
+
|
165
|
+
def banner
|
166
|
+
art = [
|
167
|
+
" . __ ___",
|
168
|
+
" _, ,_ _| , . * ._ _ / ` | ",
|
169
|
+
"(_| [ `(_] (_| | [ ) (_) \\__. _|_ v#{ArduinoCI::VERSION}",
|
170
|
+
]
|
171
|
+
|
172
|
+
pad = " " * ((WIDTH - art[2].length) / 2)
|
173
|
+
art.each { |l| puts "#{pad}#{l}" }
|
174
|
+
puts
|
175
|
+
end
|
176
|
+
|
177
|
+
# Assure that a platform exists and return its definition
|
178
|
+
def assured_platform(purpose, name, config)
|
179
|
+
platform_definition = config.platform_definition(name)
|
180
|
+
assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? }
|
181
|
+
platform_definition
|
182
|
+
end
|
183
|
+
|
184
|
+
# Return true if the file (or one of the dirs containing it) is hidden
|
185
|
+
def file_is_hidden_somewhere?(path)
|
186
|
+
# this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator
|
187
|
+
path.ascend do |part|
|
188
|
+
return true if part.basename.to_s.start_with? "."
|
189
|
+
end
|
190
|
+
false
|
191
|
+
end
|
192
|
+
|
193
|
+
# print out some files
|
194
|
+
def display_files(pathname)
|
195
|
+
# `find` doesn't follow symlinks, so we should instead
|
196
|
+
realpath = ArduinoCI::Host.symlink?(pathname) ? ArduinoCI::Host.readlink(pathname) : pathname
|
197
|
+
|
198
|
+
# suppress directories and dotfile-based things
|
199
|
+
all_files = realpath.find.select(&:file?)
|
200
|
+
non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
|
201
|
+
|
202
|
+
# print files with an indent
|
203
|
+
puts " Files (excluding hidden files): #{non_hidden.size}"
|
204
|
+
non_hidden.each { |p| puts " #{p}" }
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [Array<String>] The list of installed libraries
|
208
|
+
def install_arduino_library_dependencies(library_names, on_behalf_of, already_installed = [])
|
209
|
+
installed = already_installed.clone
|
210
|
+
(library_names.map { |n| @backend.library_of_name(n) } - installed).each do |l|
|
211
|
+
if l.installed?
|
212
|
+
inform("Using pre-existing dependency of #{on_behalf_of}") { l.name }
|
213
|
+
else
|
214
|
+
assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do
|
215
|
+
next nil unless l.install
|
216
|
+
|
217
|
+
l.name
|
218
|
+
end
|
219
|
+
end
|
220
|
+
installed << l.name
|
221
|
+
installed += install_arduino_library_dependencies(l.arduino_library_dependencies, l.name, installed)
|
222
|
+
end
|
223
|
+
installed
|
224
|
+
end
|
225
|
+
|
226
|
+
# @param platforms [Array<String>] list of platforms to consider
|
227
|
+
# @param specific_config [CIConfig] configuration to use
|
228
|
+
def install_all_packages(platforms, specific_config)
|
229
|
+
|
230
|
+
# get packages from platforms
|
231
|
+
all_packages = specific_config.platform_info.select { |p, _| platforms.include?(p) }.values.map { |v| v[:package] }.compact.uniq
|
232
|
+
|
233
|
+
all_packages.each do |pkg|
|
234
|
+
next if @backend.boards_installed?(pkg)
|
235
|
+
|
236
|
+
url = assure("Board package #{pkg} has a defined URL") { specific_config.package_url(pkg) }
|
237
|
+
@backend.board_manager_urls = [url]
|
238
|
+
assure("Installing board package #{pkg}") { @backend.install_boards(pkg) }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# @param expectation_envvar [String] the name of the env var to check
|
243
|
+
# @param operation [String] a description of what operation we might be skipping
|
244
|
+
# @param filegroup_name [String] a description of the set of files without which we effectively skip the operation
|
245
|
+
# @param dir_description [String] a description of the directory where we looked for the files
|
246
|
+
# @param dir [Pathname] the directory where we looked for the files
|
247
|
+
def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir_path)
|
248
|
+
# alert future me about running the script from the wrong directory, instead of doing the huge file dump
|
249
|
+
# otherwise, assume that the user might be running the script on a library with no actual unit tests
|
250
|
+
if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd)
|
251
|
+
inform_multiline("arduino_ci seems to be trying to test itself") do
|
252
|
+
[
|
253
|
+
"arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against",
|
254
|
+
"the core library isn't really a valid thing to do... but it's easy for a developer (including the",
|
255
|
+
"owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of",
|
256
|
+
"the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!"
|
257
|
+
].each { |l| puts " #{l}" }
|
258
|
+
false
|
259
|
+
end
|
260
|
+
exit(1)
|
261
|
+
end
|
262
|
+
|
263
|
+
# either the directory is empty, or it doesn't exist at all. message accordingly.
|
264
|
+
(problem, dir_desc, dir) = if dir_path.exist?
|
265
|
+
["No #{filegroup_name} were found in", dir_description, dir_path]
|
266
|
+
else
|
267
|
+
["No #{dir_description} at", "base directory", dir_path.parent]
|
268
|
+
end
|
269
|
+
|
270
|
+
inform(problem) { dir_path }
|
271
|
+
explain_and_exercise_envvar(expectation_envvar, operation, "contents of #{dir_desc}") { display_files(dir) }
|
272
|
+
end
|
273
|
+
|
274
|
+
# @param expectation_envvar [String] the name of the env var to check
|
275
|
+
# @param operation [String] a description of what operation we might be skipping
|
276
|
+
# @param block_desc [String] a description of what information will be dumped to assist the user
|
277
|
+
# @param block [Proc] a function that dumps information
|
278
|
+
def explain_and_exercise_envvar(expectation_envvar, operation, block_desc, &block)
|
279
|
+
inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" }
|
280
|
+
if ENV[expectation_envvar].nil?
|
281
|
+
inform_multiline("Skipping #{operation}") do
|
282
|
+
puts " In case that's an error, displaying #{block_desc}:"
|
283
|
+
block.call
|
284
|
+
puts " To force an error in this case, set the environment variable #{expectation_envvar}"
|
285
|
+
true
|
286
|
+
end
|
287
|
+
else
|
288
|
+
assure_multiline("Displaying #{block_desc} before exit") do
|
289
|
+
block.call
|
290
|
+
false
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# report and return the set of compilers
|
296
|
+
def get_annotated_compilers(config, cpp_library)
|
297
|
+
# check GCC
|
298
|
+
compilers = config.compilers_to_use
|
299
|
+
assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
|
300
|
+
compilers.each do |gcc_binary|
|
301
|
+
attempt_multiline("Checking #{gcc_binary} version") do
|
302
|
+
version = cpp_library.gcc_version(gcc_binary)
|
303
|
+
next nil unless version
|
304
|
+
|
305
|
+
puts version.split("\n").map { |l| " #{l}" }.join("\n")
|
306
|
+
version
|
307
|
+
end
|
308
|
+
inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
|
309
|
+
end
|
310
|
+
compilers
|
311
|
+
end
|
312
|
+
|
313
|
+
# Handle existence or nonexistence of custom initialization script -- run it if you have it
|
314
|
+
#
|
315
|
+
# This feature is to drive GitHub actions / docker image installation where the container is
|
316
|
+
# in a clean-slate state but needs some way to have custom library versions injected into it.
|
317
|
+
# In this case, the user provided script would fetch a git repo or some other method
|
318
|
+
def perform_custom_initialization(_config)
|
319
|
+
script_path = ENV[VAR_CUSTOM_INIT_SCRIPT]
|
320
|
+
inform("Environment variable #{VAR_CUSTOM_INIT_SCRIPT}") { "'#{script_path}'" }
|
321
|
+
return if script_path.nil?
|
322
|
+
return if script_path.empty?
|
323
|
+
|
324
|
+
script_pathname = Pathname.getwd + script_path
|
325
|
+
assure("Script at #{VAR_CUSTOM_INIT_SCRIPT} exists") { script_pathname.exist? }
|
326
|
+
|
327
|
+
assure_multiline("Running #{script_pathname} with sh in libraries working dir") do
|
328
|
+
Dir.chdir(@backend.lib_dir) do
|
329
|
+
IO.popen(["/bin/sh", script_pathname.to_s], err: [:child, :out]) do |io|
|
330
|
+
io.each_line { |line| puts " #{line}" }
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Auto-select some platforms to test based on the information available
|
337
|
+
#
|
338
|
+
# Top choice is always library.properties -- otherwise use the default.
|
339
|
+
# But filter that through any non-default config
|
340
|
+
#
|
341
|
+
# @param config [CIConfig] the overridden config object
|
342
|
+
# @param reason [String] description of why we might use this platform (i.e. unittest or compilation)
|
343
|
+
# @param desired_platforms [Array<String>] the platform names specified
|
344
|
+
# @param library_properties [Hash] the library properties defined by the library
|
345
|
+
# @return [Array<String>] platforms to use
|
346
|
+
def choose_platform_set(config, reason, desired_platforms, library_properties)
|
347
|
+
|
348
|
+
# if there are no properties or no architectures, defer entirely to desired platforms
|
349
|
+
if library_properties.nil? || library_properties.architectures.nil? || library_properties.architectures.empty?
|
350
|
+
# verify that all platforms exist
|
351
|
+
desired_platforms.each { |p| assured_platform(reason, p, config) }
|
352
|
+
return inform_multiline("No architectures listed in library.properties, using configured platforms") do
|
353
|
+
desired_platforms.each { |p| puts " #{p}" } # this returns desired_platforms
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
if library_properties.architectures.include?("*")
|
358
|
+
return inform_multiline("Wildcard architecture in library.properties, using configured platforms") do
|
359
|
+
desired_platforms.each { |p| puts " #{p}" } # this returns desired_platforms
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
platform_architecture = config.platform_info.transform_values { |v| v[:board].split(":")[1] }
|
364
|
+
supported_platforms = platform_architecture.select { |_, a| library_properties.architectures.include?(a) }
|
365
|
+
|
366
|
+
if config.is_default
|
367
|
+
# completely ignore default config, opting for brute-force library matches
|
368
|
+
# OTOH, we don't need to assure platforms because we defined them
|
369
|
+
return inform_multiline("Default config, platforms matching architectures in library.properties") do
|
370
|
+
supported_platforms.keys.each do |p| # rubocop:disable Style/HashEachMethods
|
371
|
+
puts " #{p}"
|
372
|
+
end # this returns supported_platforms
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
desired_supported_platforms = supported_platforms.select { |p, _| desired_platforms.include?(p) }.keys
|
377
|
+
desired_supported_platforms.each { |p| assured_platform(reason, p, config) }
|
378
|
+
inform_multiline("Configured platforms that match architectures in library.properties") do
|
379
|
+
desired_supported_platforms.each do |p|
|
380
|
+
puts " #{p}"
|
381
|
+
end # this returns supported_platforms
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Unit test procedure
|
386
|
+
def perform_unit_tests(cpp_library, file_config)
|
387
|
+
phase("Unit testing")
|
388
|
+
if @cli_options[:skip_unittests]
|
389
|
+
inform("Skipping unit tests") { "as requested via command line" }
|
390
|
+
return
|
391
|
+
end
|
392
|
+
|
393
|
+
config = file_config.with_override_config(@cli_options[:ci_config])
|
394
|
+
compilers = get_annotated_compilers(config, cpp_library)
|
395
|
+
|
396
|
+
inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
|
397
|
+
|
398
|
+
# Handle lack of test files
|
399
|
+
if cpp_library.test_files.empty?
|
400
|
+
handle_expectation_of_files(VAR_EXPECT_UNITTESTS, "unit tests", "test files", "tests directory", cpp_library.tests_dir)
|
401
|
+
return
|
402
|
+
end
|
403
|
+
|
404
|
+
# Get platforms, handle lack of them
|
405
|
+
platforms = choose_platform_set(config, "unittest", config.platforms_to_unittest, cpp_library.library_properties)
|
406
|
+
if platforms.empty?
|
407
|
+
explain_and_exercise_envvar(VAR_EXPECT_UNITTESTS, "unit tests", "platforms and architectures") do
|
408
|
+
puts " Configured platforms: #{config.platforms_to_unittest}"
|
409
|
+
puts " Configuration is default: #{config.is_default}"
|
410
|
+
arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
|
411
|
+
puts " Architectures in library.properties: #{arches}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")
|
416
|
+
|
417
|
+
platforms.each do |p|
|
418
|
+
puts
|
419
|
+
config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
|
420
|
+
unittest_name = unittest_path.basename.to_s
|
421
|
+
compilers.each do |gcc_binary|
|
422
|
+
attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do
|
423
|
+
exe = cpp_library.build_for_test_with_configuration(
|
424
|
+
unittest_path,
|
425
|
+
config.aux_libraries_for_unittest,
|
426
|
+
gcc_binary,
|
427
|
+
config.gcc_config(p)
|
428
|
+
)
|
429
|
+
puts
|
430
|
+
unless exe
|
431
|
+
puts "Last command: #{cpp_library.last_cmd}"
|
432
|
+
puts cpp_library.last_out
|
433
|
+
puts cpp_library.last_err
|
434
|
+
next false
|
435
|
+
end
|
436
|
+
cpp_library.run_test_file(exe)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def perform_example_compilation_tests(cpp_library, config)
|
444
|
+
phase("Compilation of example sketches")
|
445
|
+
if @cli_options[:skip_compilation]
|
446
|
+
inform("Skipping compilation of examples") { "as requested via command line" }
|
447
|
+
return
|
448
|
+
end
|
449
|
+
|
450
|
+
library_examples = cpp_library.example_sketches
|
451
|
+
|
452
|
+
if library_examples.empty?
|
453
|
+
handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the examples directory", cpp_library.examples_dir)
|
454
|
+
return
|
455
|
+
end
|
456
|
+
|
457
|
+
library_examples.each do |example_path|
|
458
|
+
example_name = File.basename(example_path)
|
459
|
+
puts
|
460
|
+
inform("Discovered example sketch") { example_name }
|
461
|
+
|
462
|
+
ovr_config = config.from_example(example_path)
|
463
|
+
platforms = choose_platform_set(ovr_config, "library example", ovr_config.platforms_to_build, cpp_library.library_properties)
|
464
|
+
|
465
|
+
if platforms.empty?
|
466
|
+
explain_and_exercise_envvar(VAR_EXPECT_EXAMPLES, "examples compilation", "platforms and architectures") do
|
467
|
+
puts " Configured platforms: #{ovr_config.platforms_to_build}"
|
468
|
+
puts " Configuration is default: #{ovr_config.is_default}"
|
469
|
+
arches = cpp_library.library_properties.nil? ? nil : cpp_library.library_properties.architectures
|
470
|
+
puts " Architectures in library.properties: #{arches}"
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
install_all_packages(platforms, ovr_config)
|
475
|
+
install_arduino_library_dependencies(ovr_config.aux_libraries_for_build, "<compile/libraries>")
|
476
|
+
|
477
|
+
platforms.each do |p|
|
478
|
+
board = ovr_config.platform_info[p][:board]
|
479
|
+
attempt("Compiling #{example_name} for #{board}") do
|
480
|
+
ret = @backend.compile_sketch(example_path, board)
|
481
|
+
unless ret
|
482
|
+
puts
|
483
|
+
puts "Last command: #{@backend.last_msg}"
|
484
|
+
puts @backend.last_err
|
485
|
+
end
|
486
|
+
ret
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
banner
|
493
|
+
inform("Host OS") { ArduinoCI::Host.os }
|
494
|
+
|
495
|
+
# initialize command and config
|
496
|
+
config = ArduinoCI::CIConfig.default.from_project_library
|
497
|
+
@backend = ArduinoCI::ArduinoInstallation.autolocate!
|
498
|
+
inform("Located arduino-cli binary") { @backend.binary_path.to_s }
|
499
|
+
if @backend.lib_dir.exist?
|
500
|
+
inform("Found libraries directory") { @backend.lib_dir }
|
501
|
+
else
|
502
|
+
assure("Creating libraries directory") { @backend.lib_dir.mkpath || true }
|
503
|
+
end
|
504
|
+
|
505
|
+
# run any library init scripts from the library itself.
|
506
|
+
perform_custom_initialization(config)
|
507
|
+
|
508
|
+
# initialize library under test
|
509
|
+
inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" }
|
510
|
+
cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR])
|
511
|
+
cpp_library = assure("Installing library under test") do
|
512
|
+
@backend.install_local_library(cpp_library_path)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Warn if the library name isn't obvious
|
516
|
+
assumed_name = @backend.name_of_library(cpp_library_path)
|
517
|
+
ondisk_name = cpp_library_path.realpath.basename.to_s
|
518
|
+
warn("Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'") if assumed_name != ondisk_name
|
519
|
+
|
520
|
+
if !cpp_library.nil?
|
521
|
+
inform("Library installed at") { cpp_library.path.to_s }
|
522
|
+
else
|
523
|
+
# this is a longwinded way of failing, we aren't really "assuring" anything at this point
|
524
|
+
assure_multiline("Library installed successfully") do
|
525
|
+
puts @backend.last_msg
|
526
|
+
false
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
install_arduino_library_dependencies(
|
531
|
+
cpp_library.arduino_library_dependencies,
|
532
|
+
"<#{ArduinoCI::CppLibrary::LIBRARY_PROPERTIES_FILE}>"
|
533
|
+
)
|
534
|
+
|
535
|
+
perform_unit_tests(cpp_library, config)
|
536
|
+
perform_example_compilation_tests(cpp_library, config)
|
537
|
+
|
538
|
+
terminate(true)
|