arduino_ci 0.3.0 → 1.3.0
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.
- 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)
|