arduino_ci 0.1.20 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -19
- data/REFERENCE.md +625 -0
- data/cpp/arduino/Arduino.h +1 -2
- data/cpp/arduino/AvrMath.h +117 -17
- data/cpp/arduino/Client.h +26 -0
- data/cpp/arduino/EEPROM.h +64 -0
- data/cpp/arduino/Godmode.cpp +38 -19
- data/cpp/arduino/Godmode.h +88 -22
- data/cpp/arduino/HardwareSerial.h +9 -28
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/MockEventQueue.h +86 -0
- data/cpp/arduino/PinHistory.h +64 -24
- 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 +234 -0
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/arduino/ci/StreamTape.h +36 -0
- data/cpp/unittest/ArduinoUnitTests.h +1 -0
- data/cpp/unittest/Compare.h +91 -897
- data/cpp/unittest/OstreamHelpers.h +9 -0
- data/exe/arduino_ci.rb +401 -0
- data/exe/arduino_ci_remote.rb +2 -385
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_cmd.rb +13 -9
- data/lib/arduino_ci/arduino_downloader.rb +5 -4
- data/lib/arduino_ci/arduino_installation.rb +5 -5
- data/lib/arduino_ci/ci_config.rb +12 -0
- data/lib/arduino_ci/cpp_library.rb +152 -25
- data/lib/arduino_ci/installed_cpp_library.rb +0 -0
- data/lib/arduino_ci/library_properties.rb +86 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +50 -3
- metadata +23 -13
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/cpp/arduino/Nullptr.h +0 -7
- data/cpp/arduino/ci/Queue.h +0 -73
- data/exe/libasan.rb +0 -29
data/lib/arduino_ci.rb
CHANGED
@@ -2,6 +2,7 @@ require "arduino_ci/version"
|
|
2
2
|
require "arduino_ci/arduino_installation"
|
3
3
|
require "arduino_ci/cpp_library"
|
4
4
|
require "arduino_ci/ci_config"
|
5
|
+
require "arduino_ci/library_properties"
|
5
6
|
|
6
7
|
# ArduinoCI contains classes for automated testing of Arduino code on the command line
|
7
8
|
# @author Ian Katz <ianfixes@gmail.com>
|
@@ -6,6 +6,9 @@ WORKAROUND_LIB = "USBHost".freeze
|
|
6
6
|
|
7
7
|
module ArduinoCI
|
8
8
|
|
9
|
+
# To report errors that we can't resolve or possibly even explain
|
10
|
+
class ArduinoExecutionError < StandardError; end
|
11
|
+
|
9
12
|
# Wrap the Arduino executable. This requires, in some cases, a faked display.
|
10
13
|
class ArduinoCmd
|
11
14
|
|
@@ -42,14 +45,14 @@ module ArduinoCI
|
|
42
45
|
attr_reader :last_msg
|
43
46
|
|
44
47
|
# set the command line flags (undefined for now).
|
45
|
-
# These vary between gui/cli
|
46
|
-
flag :get_pref
|
47
|
-
flag :set_pref
|
48
|
-
flag :save_prefs
|
49
|
-
flag :use_board
|
50
|
-
flag :install_boards
|
51
|
-
flag :install_library
|
52
|
-
flag :verify
|
48
|
+
# These vary between gui/cli. Inline comments added for greppability
|
49
|
+
flag :get_pref # flag_get_pref
|
50
|
+
flag :set_pref # flag_set_pref
|
51
|
+
flag :save_prefs # flag_save_prefs
|
52
|
+
flag :use_board # flag_use_board
|
53
|
+
flag :install_boards # flag_install_boards
|
54
|
+
flag :install_library # flag_install_library
|
55
|
+
flag :verify # flag_verify
|
53
56
|
|
54
57
|
def initialize
|
55
58
|
@prefs_cache = {}
|
@@ -82,7 +85,8 @@ module ArduinoCI
|
|
82
85
|
# @return [String] Preferences as a set of lines
|
83
86
|
def _prefs_raw
|
84
87
|
resp = run_and_capture(flag_get_pref)
|
85
|
-
|
88
|
+
fail_msg = "Arduino binary failed to operate as expected; you will have to troubleshoot it manually"
|
89
|
+
raise ArduinoExecutionError, "#{fail_msg}. The command was #{@last_msg}" unless resp[:success]
|
86
90
|
|
87
91
|
@prefs_fetched = true
|
88
92
|
resp[:out]
|
@@ -161,16 +161,17 @@ module ArduinoCI
|
|
161
161
|
return false
|
162
162
|
end
|
163
163
|
|
164
|
+
arduino_package = "Arduino #{@desired_ide_version} package"
|
164
165
|
attempts = 0
|
165
166
|
|
166
167
|
loop do
|
167
168
|
if File.exist? package_file
|
168
|
-
@output.puts "
|
169
|
+
@output.puts "#{arduino_package} seems to have been downloaded already" if attempts.zero?
|
169
170
|
break
|
170
171
|
elsif attempts >= DOWNLOAD_ATTEMPTS
|
171
172
|
break @output.puts "After #{DOWNLOAD_ATTEMPTS} attempts, failed to download #{package_url}"
|
172
173
|
else
|
173
|
-
@output.print "Attempting to download
|
174
|
+
@output.print "Attempting to download #{arduino_package} with #{downloader}"
|
174
175
|
download
|
175
176
|
@output.puts
|
176
177
|
end
|
@@ -178,7 +179,7 @@ module ArduinoCI
|
|
178
179
|
end
|
179
180
|
|
180
181
|
if File.exist? extracted_file
|
181
|
-
@output.puts "
|
182
|
+
@output.puts "#{arduino_package} seems to have been extracted already"
|
182
183
|
elsif File.exist? package_file
|
183
184
|
@output.print "Extracting archive with #{extracter}"
|
184
185
|
extract
|
@@ -186,7 +187,7 @@ module ArduinoCI
|
|
186
187
|
end
|
187
188
|
|
188
189
|
if File.exist? self.class.force_install_location
|
189
|
-
@output.puts "
|
190
|
+
@output.puts "#{arduino_package} seems to have been installed already"
|
190
191
|
elsif File.exist? extracted_file
|
191
192
|
install
|
192
193
|
else
|
@@ -110,11 +110,11 @@ module ArduinoCI
|
|
110
110
|
# Forcibly install Arduino from the web
|
111
111
|
# @return [bool] Whether the command succeeded
|
112
112
|
def force_install(output = $stdout, version = DESIRED_ARDUINO_IDE_VERSION)
|
113
|
-
worker_class =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
worker_class = case Host.os
|
114
|
+
when :osx then ArduinoDownloaderOSX
|
115
|
+
when :windows then ArduinoDownloaderWindows
|
116
|
+
when :linux then ArduinoDownloaderLinux
|
117
|
+
end
|
118
118
|
worker = worker_class.new(version, output)
|
119
119
|
worker.execute
|
120
120
|
end
|
data/lib/arduino_ci/ci_config.rb
CHANGED
@@ -28,6 +28,7 @@ UNITTEST_SCHEMA = {
|
|
28
28
|
compilers: Array,
|
29
29
|
platforms: Array,
|
30
30
|
libraries: Array,
|
31
|
+
exclude_dirs: Array,
|
31
32
|
testfiles: {
|
32
33
|
select: Array,
|
33
34
|
reject: Array,
|
@@ -256,6 +257,14 @@ module ArduinoCI
|
|
256
257
|
@unittest_info[:compilers]
|
257
258
|
end
|
258
259
|
|
260
|
+
# paths to exclude all files in for building and unitttests
|
261
|
+
# @return [Array<String>] The directories (relative to base dir) to exclude
|
262
|
+
def exclude_dirs
|
263
|
+
return [] if @unittest_info[:exclude_dirs].nil?
|
264
|
+
|
265
|
+
@unittest_info[:exclude_dirs]
|
266
|
+
end
|
267
|
+
|
259
268
|
# platforms to build [the examples on]
|
260
269
|
# @return [Array<String>] The platforms to build
|
261
270
|
def platforms_to_build
|
@@ -293,9 +302,12 @@ module ArduinoCI
|
|
293
302
|
return paths if @unittest_info[:testfiles].nil?
|
294
303
|
|
295
304
|
ret = paths
|
305
|
+
# Check for array emptiness, otherwise nothing will be selected!
|
296
306
|
unless @unittest_info[:testfiles][:select].nil? || @unittest_info[:testfiles][:select].empty?
|
297
307
|
ret.select! { |p| unittest_info[:testfiles][:select].any? { |glob| p.basename.fnmatch(glob) } }
|
298
308
|
end
|
309
|
+
|
310
|
+
# It's OK for the :reject array to be empty, that means nothing will be rejected by default
|
299
311
|
unless @unittest_info[:testfiles][:reject].nil?
|
300
312
|
ret.reject! { |p| unittest_info[:testfiles][:reject].any? { |glob| p.basename.fnmatch(glob) } }
|
301
313
|
end
|
@@ -37,11 +37,15 @@ module ArduinoCI
|
|
37
37
|
|
38
38
|
# @param base_dir [Pathname] The path to the library being tested
|
39
39
|
# @param arduino_lib_dir [Pathname] The path to the libraries directory
|
40
|
-
|
40
|
+
# @param exclude_dirs [Array<Pathname>] Directories that should be excluded from compilation
|
41
|
+
def initialize(base_dir, arduino_lib_dir, exclude_dirs)
|
41
42
|
raise ArgumentError, 'base_dir is not a Pathname' unless base_dir.is_a? Pathname
|
42
43
|
raise ArgumentError, 'arduino_lib_dir is not a Pathname' unless arduino_lib_dir.is_a? Pathname
|
44
|
+
raise ArgumentError, 'exclude_dir is not an array of Pathnames' unless exclude_dirs.is_a?(Array)
|
45
|
+
raise ArgumentError, 'exclude_dir array contains non-Pathname elements' unless exclude_dirs.all? { |p| p.is_a? Pathname }
|
43
46
|
|
44
47
|
@base_dir = base_dir
|
48
|
+
@exclude_dirs = exclude_dirs
|
45
49
|
@arduino_lib_dir = arduino_lib_dir.expand_path
|
46
50
|
@artifacts = []
|
47
51
|
@last_err = ""
|
@@ -51,6 +55,32 @@ module ArduinoCI
|
|
51
55
|
@vendor_bundle_cache = nil
|
52
56
|
end
|
53
57
|
|
58
|
+
# The expected path to the library.properties file (i.e. even if it does not exist)
|
59
|
+
# @return [Pathname]
|
60
|
+
def library_properties_path
|
61
|
+
@base_dir + "library.properties"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Whether library.properties definitions for this library exist
|
65
|
+
# @return [bool]
|
66
|
+
def library_properties?
|
67
|
+
lib_props = library_properties_path
|
68
|
+
lib_props.exist? && lib_props.file?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Decide whether this is a 1.5-compatible library
|
72
|
+
#
|
73
|
+
# according to https://arduino.github.io/arduino-cli/latest/library-specification
|
74
|
+
#
|
75
|
+
# Should match logic from https://github.com/arduino/arduino-cli/blob/master/arduino/libraries/loader.go
|
76
|
+
# @return [bool]
|
77
|
+
def one_point_five?
|
78
|
+
return false unless library_properties?
|
79
|
+
|
80
|
+
src_dir = (@base_dir + "src")
|
81
|
+
src_dir.exist? && src_dir.directory?
|
82
|
+
end
|
83
|
+
|
54
84
|
# Guess whether a file is part of the vendor bundle (indicating we should ignore it).
|
55
85
|
#
|
56
86
|
# A safe way to do this seems to be to check whether any of the installed gems
|
@@ -106,6 +136,8 @@ module ArduinoCI
|
|
106
136
|
# @param path [Pathname] The path to check
|
107
137
|
# @return [bool]
|
108
138
|
def in_tests_dir?(path)
|
139
|
+
return false unless tests_dir.exist?
|
140
|
+
|
109
141
|
tests_dir_aliases = [tests_dir, tests_dir.realpath]
|
110
142
|
# we could do this but some rubies don't return an enumerator for ascend
|
111
143
|
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
@@ -115,6 +147,19 @@ module ArduinoCI
|
|
115
147
|
false
|
116
148
|
end
|
117
149
|
|
150
|
+
# Guess whether a file is part of any @excludes_dir dir (indicating library compilation should ignore it).
|
151
|
+
#
|
152
|
+
# @param path [Pathname] The path to check
|
153
|
+
# @return [bool]
|
154
|
+
def in_exclude_dir?(path)
|
155
|
+
# we could do this but some rubies don't return an enumerator for ascend
|
156
|
+
# path.ascend.any? { |part| tests_dir_aliases.include?(part) }
|
157
|
+
path.ascend do |part|
|
158
|
+
return true if exclude_dir.any? { |p| p.realpath == part }
|
159
|
+
end
|
160
|
+
false
|
161
|
+
end
|
162
|
+
|
118
163
|
# Check whether libasan (and by extension -fsanitizer=address) is supported
|
119
164
|
#
|
120
165
|
# This requires compilation of a sample program, and will be cached
|
@@ -133,43 +178,98 @@ module ArduinoCI
|
|
133
178
|
@has_libasan_cache[gcc_binary]
|
134
179
|
end
|
135
180
|
|
181
|
+
# Library properties
|
182
|
+
def library_properties
|
183
|
+
return nil unless library_properties?
|
184
|
+
|
185
|
+
LibraryProperties.new(library_properties_path)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get a list of all dependencies as defined in library.properties
|
189
|
+
# @return [Array<String>] The library names of the dependencies (not the paths)
|
190
|
+
def arduino_library_dependencies
|
191
|
+
return nil unless library_properties?
|
192
|
+
|
193
|
+
library_properties.depends
|
194
|
+
end
|
195
|
+
|
136
196
|
# Get a list of all CPP source files in a directory and its subdirectories
|
137
197
|
# @param some_dir [Pathname] The directory in which to begin the search
|
198
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
138
199
|
# @return [Array<Pathname>] The paths of the found files
|
139
|
-
def
|
200
|
+
def code_files_in(some_dir, extensions)
|
140
201
|
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
141
202
|
return [] unless some_dir.exist? && some_dir.directory?
|
142
203
|
|
143
|
-
|
144
|
-
|
145
|
-
cpp = files.select { |path| CPP_EXTENSIONS.include?(path.extname.downcase) }
|
204
|
+
files = some_dir.realpath.children.reject(&:directory?)
|
205
|
+
cpp = files.select { |path| extensions.include?(path.extname.downcase) }
|
146
206
|
not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
|
147
207
|
not_hidden.sort_by(&:to_s)
|
148
208
|
end
|
149
209
|
|
210
|
+
# Get a list of all CPP source files in a directory and its subdirectories
|
211
|
+
# @param some_dir [Pathname] The directory in which to begin the search
|
212
|
+
# @param extensions [Array<Sring>] The set of allowable file extensions
|
213
|
+
# @return [Array<Pathname>] The paths of the found files
|
214
|
+
def code_files_in_recursive(some_dir, extensions)
|
215
|
+
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
|
216
|
+
return [] unless some_dir.exist? && some_dir.directory?
|
217
|
+
|
218
|
+
real = some_dir.realpath
|
219
|
+
Find.find(real).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
|
220
|
+
end
|
221
|
+
|
222
|
+
# Header files that are part of the project library under test
|
223
|
+
# @return [Array<Pathname>]
|
224
|
+
def header_files
|
225
|
+
ret = if one_point_five?
|
226
|
+
code_files_in_recursive(@base_dir + "src", HPP_EXTENSIONS)
|
227
|
+
else
|
228
|
+
[@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, HPP_EXTENSIONS) }.flatten
|
229
|
+
end
|
230
|
+
|
231
|
+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
|
232
|
+
# least some of them are tied to existing features
|
233
|
+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
|
234
|
+
end
|
235
|
+
|
150
236
|
# CPP files that are part of the project library under test
|
151
237
|
# @return [Array<Pathname>]
|
152
238
|
def cpp_files
|
153
|
-
|
239
|
+
ret = if one_point_five?
|
240
|
+
code_files_in_recursive(@base_dir + "src", CPP_EXTENSIONS)
|
241
|
+
else
|
242
|
+
[@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten
|
243
|
+
end
|
244
|
+
|
245
|
+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
|
246
|
+
# least some of them are tied to existing features
|
247
|
+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
|
154
248
|
end
|
155
249
|
|
156
250
|
# CPP files that are part of the arduino mock library we're providing
|
157
251
|
# @return [Array<Pathname>]
|
158
252
|
def cpp_files_arduino
|
159
|
-
|
253
|
+
code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
|
160
254
|
end
|
161
255
|
|
162
256
|
# CPP files that are part of the unit test library we're providing
|
163
257
|
# @return [Array<Pathname>]
|
164
258
|
def cpp_files_unittest
|
165
|
-
|
259
|
+
code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
|
166
260
|
end
|
167
261
|
|
168
262
|
# CPP files that are part of the 3rd-party libraries we're including
|
169
263
|
# @param [Array<String>] aux_libraries
|
170
264
|
# @return [Array<Pathname>]
|
171
265
|
def cpp_files_libraries(aux_libraries)
|
172
|
-
arduino_library_src_dirs(aux_libraries).map { |d|
|
266
|
+
arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
|
267
|
+
end
|
268
|
+
|
269
|
+
# Returns the Pathnames for all paths to exclude from testing and compilation
|
270
|
+
# @return [Array<Pathname>]
|
271
|
+
def exclude_dir
|
272
|
+
@exclude_dirs.map { |p| Pathname.new(@base_dir) + p }.select(&:exist?)
|
173
273
|
end
|
174
274
|
|
175
275
|
# The directory where we expect to find unit test defintions provided by the user
|
@@ -181,16 +281,15 @@ module ArduinoCI
|
|
181
281
|
# The files provided by the user that contain unit tests
|
182
282
|
# @return [Array<Pathname>]
|
183
283
|
def test_files
|
184
|
-
|
284
|
+
code_files_in(tests_dir, CPP_EXTENSIONS)
|
185
285
|
end
|
186
286
|
|
187
287
|
# Find all directories in the project library that include C++ header files
|
188
288
|
# @return [Array<Pathname>]
|
189
289
|
def header_dirs
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
files = unbundled.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
290
|
+
unbundled = header_files.reject { |path| vendor_bundle?(path) }
|
291
|
+
unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
|
292
|
+
files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
|
194
293
|
files.map(&:dirname).uniq
|
195
294
|
end
|
196
295
|
|
@@ -212,20 +311,24 @@ module ArduinoCI
|
|
212
311
|
@last_err
|
213
312
|
end
|
214
313
|
|
215
|
-
# Arduino library directories containing sources
|
314
|
+
# Arduino library directories containing sources -- only those of the dependencies
|
216
315
|
# @return [Array<Pathname>]
|
217
316
|
def arduino_library_src_dirs(aux_libraries)
|
218
317
|
# Pull in all possible places that headers could live, according to the spec:
|
219
318
|
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
319
|
+
|
320
|
+
aux_libraries.map do |d|
|
321
|
+
# library manager coerces spaces in package names to underscores
|
322
|
+
# see https://github.com/ianfixes/arduino_ci/issues/132#issuecomment-518857059
|
323
|
+
legal_dir = d.tr(" ", "_")
|
324
|
+
self.class.new(@arduino_lib_dir + legal_dir, @arduino_lib_dir, @exclude_dirs).header_dirs
|
325
|
+
end.flatten.uniq
|
226
326
|
end
|
227
327
|
|
228
328
|
# GCC command line arguments for including aux libraries
|
329
|
+
#
|
330
|
+
# This function recursively collects the library directores of the dependencies
|
331
|
+
#
|
229
332
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
230
333
|
# @return [Array<String>] The GCC command-line flags necessary to include those libraries
|
231
334
|
def include_args(aux_libraries)
|
@@ -288,6 +391,9 @@ module ArduinoCI
|
|
288
391
|
end
|
289
392
|
|
290
393
|
# build a file for running a test of the given unit test file
|
394
|
+
#
|
395
|
+
# The dependent libraries configuration is appended with data from library.properties internal to the library under test
|
396
|
+
#
|
291
397
|
# @param test_file [Pathname] The path to the file containing the unit tests
|
292
398
|
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
|
293
399
|
# @param ci_gcc_config [Hash] The GCC config object
|
@@ -306,8 +412,12 @@ module ArduinoCI
|
|
306
412
|
"-fsanitize=address"
|
307
413
|
]
|
308
414
|
end
|
309
|
-
|
310
|
-
|
415
|
+
|
416
|
+
# combine library.properties defs (if existing) with config file.
|
417
|
+
# TODO: as much as I'd like to rely only on the properties file(s), I think that would prevent testing 1.0-spec libs
|
418
|
+
full_aux_libraries = arduino_library_dependencies.nil? ? aux_libraries : aux_libraries + arduino_library_dependencies
|
419
|
+
arg_sets << test_args(full_aux_libraries, ci_gcc_config)
|
420
|
+
arg_sets << cpp_files_libraries(full_aux_libraries).map(&:to_s)
|
311
421
|
arg_sets << [test_file.to_s]
|
312
422
|
args = arg_sets.flatten(1)
|
313
423
|
return nil unless run_gcc(gcc_binary, *args)
|
@@ -316,14 +426,31 @@ module ArduinoCI
|
|
316
426
|
executable
|
317
427
|
end
|
318
428
|
|
429
|
+
# print any found stack dumps
|
430
|
+
# @param executable [Pathname] the path to the test file
|
431
|
+
def print_stack_dump(executable)
|
432
|
+
possible_dumpfiles = [
|
433
|
+
executable.sub_ext(executable.extname + ".stackdump")
|
434
|
+
]
|
435
|
+
possible_dumpfiles.select(&:exist?).each do |dump|
|
436
|
+
puts "========== Stack dump from #{dump}:"
|
437
|
+
File.foreach(dump) { |line| print " #{line}" }
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
319
441
|
# run a test file
|
320
|
-
# @param [Pathname] the path to the test file
|
442
|
+
# @param executable [Pathname] the path to the test file
|
321
443
|
# @return [bool] whether all tests were successful
|
322
444
|
def run_test_file(executable)
|
323
445
|
@last_cmd = executable
|
324
446
|
@last_out = ""
|
325
447
|
@last_err = ""
|
326
|
-
Host.run_and_output(executable.to_s.shellescape)
|
448
|
+
ret = Host.run_and_output(executable.to_s.shellescape)
|
449
|
+
|
450
|
+
# print any stack traces found during a failure
|
451
|
+
print_stack_dump(executable) unless ret
|
452
|
+
|
453
|
+
ret
|
327
454
|
end
|
328
455
|
|
329
456
|
end
|
File without changes
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ArduinoCI
|
2
|
+
|
3
|
+
# Information about an Arduino library package, as specified by the library.properties file
|
4
|
+
#
|
5
|
+
# See https://arduino.github.io/arduino-cli/library-specification/#libraryproperties-file-format
|
6
|
+
class LibraryProperties
|
7
|
+
|
8
|
+
# @return [Hash] The properties file parsed as a hash
|
9
|
+
attr_reader :fields
|
10
|
+
|
11
|
+
# @param path [Pathname] The path to the library.properties file
|
12
|
+
def initialize(path)
|
13
|
+
@fields = {}
|
14
|
+
File.foreach(path) do |line|
|
15
|
+
parts = line.split("=", 2)
|
16
|
+
@fields[parts[0]] = parts[1].chomp unless parts.empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Enable a shortcut syntax for library property accessors, in the style of `attr_accessor` metaprogramming.
|
21
|
+
# This is used to create a named field pointing to a specific property in the file, optionally applying
|
22
|
+
# a specific formatting function.
|
23
|
+
#
|
24
|
+
# The formatting function MUST be a static method on this class. This is a limitation caused by the desire
|
25
|
+
# to both (1) expose the formatters outside this class, and (2) use them for metaprogramming without the
|
26
|
+
# having to name the entire function. field_reader is a static method, so if not for the fact that
|
27
|
+
# `self.class.methods.include? formatter` fails to work for class methods in this context (unlike
|
28
|
+
# `self.methods.include?`, which properly finds instance methods), I would allow either one and just
|
29
|
+
# conditionally `define_method` the proper definition
|
30
|
+
#
|
31
|
+
# @param name [String] What the accessor will be called
|
32
|
+
# @param field_num [Integer] The name of the key of the property
|
33
|
+
# @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional)
|
34
|
+
# @return [void]
|
35
|
+
# @macro [attach] field_reader
|
36
|
+
# @!attribute [r] $1
|
37
|
+
# @return property $2 of the library.properties file, formatted with the function {$3}
|
38
|
+
def self.field_reader(name, formatter = nil)
|
39
|
+
key = name.to_s
|
40
|
+
if formatter.nil?
|
41
|
+
define_method(name) { @fields[key] }
|
42
|
+
else
|
43
|
+
define_method(name) { @fields.key?(key) ? self.class.send(formatter.to_sym, @fields[key]) : nil }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parse a value as a comma-separated array
|
48
|
+
# @param input [String]
|
49
|
+
# @return [Array<String>] The individual values
|
50
|
+
def self._csv(input)
|
51
|
+
input.split(",").map(&:strip)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Parse a value as a boolean
|
55
|
+
# @param input [String]
|
56
|
+
# @return [Array<String>] The individual values
|
57
|
+
def self._bool(input)
|
58
|
+
input == "true" # no indication given in the docs that anything but lowercase "true" indicates boolean true.
|
59
|
+
end
|
60
|
+
|
61
|
+
field_reader :name
|
62
|
+
field_reader :version
|
63
|
+
field_reader :author, :_csv
|
64
|
+
field_reader :maintainer
|
65
|
+
field_reader :sentence
|
66
|
+
field_reader :paragraph
|
67
|
+
field_reader :category
|
68
|
+
field_reader :url
|
69
|
+
field_reader :architectures, :_csv
|
70
|
+
field_reader :depends, :_csv
|
71
|
+
field_reader :dot_a_linkage, :_bool
|
72
|
+
field_reader :includes, :_csv
|
73
|
+
field_reader :precompiled, :_bool
|
74
|
+
field_reader :ldflags, :_csv
|
75
|
+
|
76
|
+
# The value of sentence always will be prepended, so you should start by writing the second sentence here
|
77
|
+
#
|
78
|
+
# (according to the docs)
|
79
|
+
# @return [String] the sentence and paragraph together
|
80
|
+
def full_paragraph
|
81
|
+
[sentence, paragraph].join(" ")
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|