arduino_ci 0.1.17 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a089a7486b4295c2ed49a832a0dd148d6e31561a
4
- data.tar.gz: 0c13b415b9b585c42ec2a8501c5e6ef606137c30
3
+ metadata.gz: d7de3ae0cd1c73eb41fe0b7f897e20f7a3d2e5c0
4
+ data.tar.gz: 29dc639c29a0dcb3841e3dd903002e1fc72ef892
5
5
  SHA512:
6
- metadata.gz: 74819da0c7bbaf23fbd5ceaf1952e43442d0bdd4789ecf1beb4c3a110038350797b9b6629698a3348bc9803afb444344badc2f0bdebbc930bf42835bb07b3109
7
- data.tar.gz: 794e75954f74a2e9420d6ec4e884683b86f2a04d2ac0b0a3cf13776126e8db40c29724ebe397fb2d9bd062c5e3044cea206ed078aa5958dee825cd902d38e5a9
6
+ metadata.gz: cf8434e69d4b3cc1c65d4b37db875133fdd1055e2d435b3936c15ef1e731c9160238a597ed2e2f8c21d28ce4522963e02371482ddfebc00307b2b16c2bfebf9d
7
+ data.tar.gz: db0f703d2d7dad26ccc1a54f098e64ef9953ef66266f4379e1936ff915dfb38e04899304ca666ea79fac8e1dee52868979b96d6ef2937a7a085a6383dec1f176
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- # ArduinoCI Ruby gem (`arduino_ci`) [![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.1.17)
2
+ # ArduinoCI Ruby gem (`arduino_ci`) [![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.1.18)
3
3
 
4
4
  You want your Arduino library to be automatically built and tested every time someone contributes code to your project on GitHub, but the Arduino IDE lacks the ability to run unit tests. [Arduino CI](https://github.com/ianfixes/arduino_ci) provides that ability.
5
5
 
@@ -72,5 +72,7 @@ inline unsigned int makeWord(unsigned char h, unsigned char l) { return (h << 8)
72
72
  #define word(...) makeWord(__VA_ARGS__)
73
73
 
74
74
 
75
+ // Define C++11 nullptr
76
+ #define nullptr (std::nullptr_t)NULL
75
77
 
76
78
 
@@ -29,25 +29,26 @@ out.each { |l| puts d(l) }
29
29
 
30
30
  // everything's a no-op
31
31
  #define PSTR(s) ((const char *)(s))
32
- #define pgm_read_byte_near(x) (x)
33
- #define pgm_read_word_near(x) (x)
34
- #define pgm_read_dword_near(x) (x)
35
- #define pgm_read_float_near(x) (x)
36
- #define pgm_read_ptr_near(x) (x)
37
32
 
38
- #define pgm_read_byte_far(x) (x)
39
- #define pgm_read_word_far(x) (x)
40
- #define pgm_read_dword_far(x) (x)
41
- #define pgm_read_float_far(x) (x)
42
- #define pgm_read_ptr_far(x) (x)
33
+ #define pgm_read_byte_near(address_short) (* (const uint8_t *) (address_short) )
34
+ #define pgm_read_word_near(address_short) (* (const uint16_t *) (address_short) )
35
+ #define pgm_read_dword_near(address_short) (* (const uint32_t *) (address_short) )
36
+ #define pgm_read_float_near(address_short) (* (const float *) (address_short) )
37
+ #define pgm_read_ptr_near(address_short) (* (const void *) (address_short) )
43
38
 
39
+ #define pgm_read_byte_far(address_long) (* (const uint8_t *) (address_long) )
40
+ #define pgm_read_word_far(address_long) (* (const uint16_t *) (address_long) )
41
+ #define pgm_read_dword_far(address_long) (* (const uint32_t *) (address_long) )
42
+ #define pgm_read_float_far(address_long) (* (const float *) (address_long) )
43
+ #define pgm_read_ptr_far(address_long) (* (const void *) (address_long) )
44
44
 
45
- #define pgm_read_byte(addr) (*(const uint8_t *)(addr))
46
- #define pgm_read_word(x) (x)
47
- #define pgm_read_dword(x) (x)
48
- #define pgm_read_float(x) (x)
49
- #define pgm_read_ptr(x) (x)
50
- #define pgm_get_far_address(x) (x)
45
+ #define pgm_read_byte(address_short) pgm_read_byte_near(address_short)
46
+ #define pgm_read_word(address_short) pgm_read_word_near(address_short)
47
+ #define pgm_read_dword(address_short) pgm_read_dword_near(address_short)
48
+ #define pgm_read_float(address_short) pgm_read_float_near(address_short)
49
+ #define pgm_read_ptr(address_short) pgm_read_ptr_near(address_short)
50
+
51
+ #define pgm_get_far_address(var) ( (uint_farptr_t) (&(var)) )
51
52
 
52
53
  #define memchr_P(...) ::memchr(__VA_ARGS__)
53
54
  #define memcmp_P(...) ::memcmp(__VA_ARGS__)
@@ -1,3 +1,6 @@
1
1
  #include "ArduinoUnitTests.h"
2
2
 
3
+ TestSetup *TestSetup::sInstance;
4
+ TestTeardown *TestTeardown::sInstance;
5
+
3
6
  Test* Test::sRoot = 0;
@@ -15,6 +15,26 @@ struct TestData {
15
15
  int result;
16
16
  };
17
17
 
18
+ class TestSetup
19
+ {
20
+ public:
21
+ TestSetup() {
22
+ sInstance = this;
23
+ }
24
+ virtual void run() {}
25
+ static TestSetup *sInstance;
26
+ };
27
+
28
+ class TestTeardown
29
+ {
30
+ public:
31
+ TestTeardown() {
32
+ sInstance = this;
33
+ }
34
+ virtual void run() {}
35
+ static TestTeardown *sInstance;
36
+ };
37
+
18
38
  class Test
19
39
  {
20
40
  public:
@@ -49,29 +69,29 @@ class Test
49
69
  }
50
70
 
51
71
  template <typename A, typename B> void onAssert(
52
- const char* file,
53
- int line,
54
- const char* description,
55
- bool pass,
56
- const char* lhsRelevance,
57
- const char* lhsLabel,
58
- const A &lhs,
59
- const char* opLabel,
60
- const char* rhsRelevance,
61
- const char* rhsLabel,
62
- const B &rhs
63
- ) {
64
- cerr << " " << (pass ? "" : "not ") << "ok " << ++mAssertCounter << " - ";
65
- cerr << description << " " << lhsLabel << " " << opLabel << " " << rhsLabel << endl;
66
- if (!pass) {
67
- cerr << " ---" << endl;
68
- cerr << " operator: " << opLabel << endl;
69
- cerr << " " << lhsRelevance << ": " << lhs << endl;
70
- cerr << " " << rhsRelevance << ": " << rhs << endl;
71
- cerr << " at:" << endl;
72
- cerr << " file: " << file << endl;
73
- cerr << " line: " << line << endl;
74
- cerr << " ..." << endl;
72
+ const char* file,
73
+ int line,
74
+ const char* description,
75
+ bool pass,
76
+ const char* lhsRelevance,
77
+ const char* lhsLabel,
78
+ const A &lhs,
79
+ const char* opLabel,
80
+ const char* rhsRelevance,
81
+ const char* rhsLabel,
82
+ const B &rhs
83
+ ) {
84
+ cerr << " " << (pass ? "" : "not ") << "ok " << ++mAssertCounter << " - ";
85
+ cerr << description << " " << lhsLabel << " " << opLabel << " " << rhsLabel << endl;
86
+ if (!pass) {
87
+ cerr << " ---" << endl;
88
+ cerr << " operator: " << opLabel << endl;
89
+ cerr << " " << lhsRelevance << ": " << lhs << endl;
90
+ cerr << " " << rhsRelevance << ": " << rhs << endl;
91
+ cerr << " at:" << endl;
92
+ cerr << " file: " << file << endl;
93
+ cerr << " line: " << line << endl;
94
+ cerr << " ..." << endl;
75
95
  }
76
96
  }
77
97
  };
@@ -152,7 +172,9 @@ class Test
152
172
  static int run_and_report(int argc, char *argv[]) {
153
173
  // TODO: pick a reporter based on args
154
174
  ReporterTAP rep;
175
+ if (TestSetup::sInstance) TestSetup::sInstance->run();
155
176
  Results results = run(&rep);
177
+ if (TestTeardown::sInstance) TestTeardown::sInstance->run();
156
178
  return results.failed + results.skipped;
157
179
  }
158
180
 
@@ -217,6 +239,24 @@ class Test
217
239
  } test_##name##_instance; \
218
240
  void test_##name ::task()
219
241
 
242
+ /**
243
+ * The unittest_setup and unittest_teardown functions are intended to be used
244
+ * to set up "external" dependencies that needs to present but are not directly
245
+ * related to the functionality that you are testing, for instance a LCD.
246
+ */
247
+ #define unittest_setup() \
248
+ class unittest_setup_class : public TestSetup { \
249
+ public: \
250
+ virtual void run() override; \
251
+ } unittest_setup_instance; \
252
+ void unittest_setup_class::run()
253
+
254
+ #define unittest_teardown() \
255
+ class unittest_teardown_class : public TestTeardown { \
256
+ public: \
257
+ virtual void run() override; \
258
+ } unittest_teardown_instance; \
259
+ void unittest_teardown_class::run()
220
260
 
221
261
  #define unittest_main() \
222
262
  int main(int argc, char *argv[]) { \
@@ -38,6 +38,7 @@
38
38
  #define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2)
39
39
  #define assertTrue(arg) assertEqual(true, arg)
40
40
  #define assertFalse(arg) assertEqual(false, arg)
41
+ #define assertNull(arg) assertEqual((void*)NULL, (void*)arg)
41
42
 
42
43
  /** macro generates optional output and calls fail() followed by a return if false. */
43
44
  #define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,compareEqual,"==","actual",arg2)
@@ -48,4 +49,5 @@
48
49
  #define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2)
49
50
  #define assureTrue(arg) assureEqual(true, arg)
50
51
  #define assureFalse(arg) assureEqual(false, arg)
52
+ #define assureNull(arg) assureEqual((void*)NULL, (void*)arg)
51
53
 
@@ -13,22 +13,36 @@ FIND_FILES_INDENT = 4
13
13
  # Use some basic parsing to allow command-line overrides of config
14
14
  class Parser
15
15
  def self.parse(options)
16
- parsed_config = {}
17
- parsed_config["unittest"] = {}
16
+ unit_config = {}
17
+ output_options = {
18
+ skip_unittests: false,
19
+ skip_compilation: false,
20
+ ci_config: {
21
+ "unittest" => unit_config
22
+ },
23
+ }
18
24
 
19
25
  opt_parser = OptionParser.new do |opts|
20
26
  opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
21
27
 
28
+ opts.on("--skip-unittests", "Don't run unit tests") do |p|
29
+ output_options[:skip_unittests] = p
30
+ end
31
+
32
+ opts.on("--skip-compilation", "Don't compile example sketches") do |p|
33
+ output_options[:skip_compilation] = p
34
+ end
35
+
22
36
  opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
23
- parsed_config["unittest"]["testfiles"] ||= {}
24
- parsed_config["unittest"]["testfiles"]["select"] ||= []
25
- parsed_config["unittest"]["testfiles"]["select"] << p
37
+ unit_config["testfiles"] ||= {}
38
+ unit_config["testfiles"]["select"] ||= []
39
+ unit_config["testfiles"]["select"] << p
26
40
  end
27
41
 
28
42
  opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
29
- parsed_config["unittest"]["testfiles"] ||= {}
30
- parsed_config["unittest"]["testfiles"]["reject"] ||= []
31
- parsed_config["unittest"]["testfiles"]["reject"] << p
43
+ unit_config["testfiles"] ||= {}
44
+ unit_config["testfiles"]["reject"] ||= []
45
+ unit_config["testfiles"]["reject"] << p
32
46
  end
33
47
 
34
48
  opts.on("-h", "--help", "Prints this help") do
@@ -38,7 +52,7 @@ class Parser
38
52
  end
39
53
 
40
54
  opt_parser.parse!(options)
41
- parsed_config
55
+ output_options
42
56
  end
43
57
  end
44
58
 
@@ -154,9 +168,11 @@ def display_files(pathname)
154
168
  end
155
169
 
156
170
  def perform_unit_tests(file_config)
157
- puts file_config.to_h[:unittest].to_s
158
- config = file_config.with_override_config(@cli_options)
159
- puts config.to_h[:unittest].to_s
171
+ if @cli_options[:skip_unittests]
172
+ inform("Skipping unit tests") { "as requested via command line" }
173
+ return
174
+ end
175
+ config = file_config.with_override_config(@cli_options[:ci_config])
160
176
  cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."), @arduino_cmd.lib_dir)
161
177
 
162
178
  # check GCC
@@ -220,6 +236,10 @@ def perform_unit_tests(file_config)
220
236
  end
221
237
 
222
238
  def perform_compilation_tests(config)
239
+ if @cli_options[:skip_compilation]
240
+ inform("Skipping compilation of examples") { "as requested via command line" }
241
+ return
242
+ end
223
243
 
224
244
  # index the existing libraries
225
245
  attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
@@ -228,17 +248,22 @@ def perform_compilation_tests(config)
228
248
  installed_library_path = attempt("Installing library under test") do
229
249
  @arduino_cmd.install_local_library(Pathname.new("."))
230
250
  end
231
- if installed_library_path.exist?
251
+
252
+ if !installed_library_path.nil? && installed_library_path.exist?
232
253
  inform("Library installed at") { installed_library_path.to_s }
233
254
  else
234
255
  assure_multiline("Library installed successfully") do
235
- # print out the contents of the deepest directory we actually find
236
- @arduino_cmd.lib_dir.ascend do |path_part|
237
- next unless path_part.exist?
238
-
239
- break display_files(path_part)
256
+ if installed_library_path.nil?
257
+ puts @arduino_cmd.last_msg
258
+ else
259
+ # print out the contents of the deepest directory we actually find
260
+ @arduino_cmd.lib_dir.ascend do |path_part|
261
+ next unless path_part.exist?
262
+
263
+ break display_files(path_part)
264
+ end
265
+ false
240
266
  end
241
- false
242
267
  end
243
268
  end
244
269
  library_examples = @arduino_cmd.library_examples(installed_library_path)
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arduino_ci'
3
+ require 'set'
4
+ require 'pathname'
5
+ require 'optparse'
6
+
7
+ WIDTH = 80
8
+ FIND_FILES_INDENT = 4
9
+
10
+ @failure_count = 0
11
+ @passfail = proc { |result| result ? "✓" : "✗" }
12
+
13
+ # Use some basic parsing to allow command-line overrides of config
14
+ class Parser
15
+ def self.parse(options)
16
+ unit_config = {}
17
+ output_options = {
18
+ skip_unittests: false,
19
+ skip_compilation: false,
20
+ ci_config: {
21
+ "unittest" => unit_config
22
+ },
23
+ }
24
+
25
+ opt_parser = OptionParser.new do |opts|
26
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
27
+
28
+ opts.on("--skip-unittests", "Don't run unit tests") do |p|
29
+ output_options[:skip_unittests] = p
30
+ end
31
+
32
+ opts.on("--skip-compilation", "Don't compile example sketches") do |p|
33
+ output_options[:skip_compilation] = p
34
+ end
35
+
36
+ opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p|
37
+ unit_config["testfiles"] ||= {}
38
+ unit_config["testfiles"]["select"] ||= []
39
+ unit_config["testfiles"]["select"] << p
40
+ end
41
+
42
+ opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p|
43
+ unit_config["testfiles"] ||= {}
44
+ unit_config["testfiles"]["reject"] ||= []
45
+ unit_config["testfiles"]["reject"] << p
46
+ end
47
+
48
+ opts.on("-h", "--help", "Prints this help") do
49
+ puts opts
50
+ exit
51
+ end
52
+ end
53
+
54
+ opt_parser.parse!(options)
55
+ output_options
56
+ end
57
+ end
58
+
59
+ # Read in command line options and make them read-only
60
+ @cli_options = (Parser.parse ARGV).freeze
61
+
62
+ # terminate after printing any debug info. TODO: capture debug info
63
+ def terminate(final = nil)
64
+ puts "Failures: #{@failure_count}"
65
+ unless @failure_count.zero? || final
66
+ puts "Last message: #{@arduino_cmd.last_msg}"
67
+ puts "========== Stdout:"
68
+ puts @arduino_cmd.last_out
69
+ puts "========== Stderr:"
70
+ puts @arduino_cmd.last_err
71
+ end
72
+ retcode = @failure_count.zero? ? 0 : 1
73
+ exit(retcode)
74
+ end
75
+
76
+ # make a nice status line for an action and react to the action
77
+ # TODO / note to self: inform_multline is tougher to write
78
+ # without altering the signature because it only leaves space
79
+ # for the checkmark _after_ the multiline, it doesn't know how
80
+ # to make that conditionally the body
81
+ # @param message String the text of the progress indicator
82
+ # @param multiline boolean whether multiline output is expected
83
+ # @param mark_fn block (string) -> string that says how to describe the result
84
+ # @param on_fail_msg String custom message for failure
85
+ # @param tally_on_fail boolean whether to increment @failure_count
86
+ # @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error)
87
+ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail)
88
+ line = "#{message}... "
89
+ endline = "...#{message} "
90
+ if multiline
91
+ puts line
92
+ else
93
+ print line
94
+ end
95
+ STDOUT.flush
96
+ result = yield
97
+ mark = mark_fn.nil? ? "" : mark_fn.call(result)
98
+ # if multline, put checkmark at full width
99
+ print endline if multiline
100
+ puts mark.to_s.rjust(WIDTH - line.length, " ")
101
+ unless result
102
+ puts on_fail_msg unless on_fail_msg.nil?
103
+ @failure_count += 1 if tally_on_fail
104
+ # print out error messaging here if we've captured it
105
+ terminate if abort_on_fail
106
+ end
107
+ result
108
+ end
109
+
110
+ # Make a nice status for something that defers any failure code until script exit
111
+ def attempt(message, &block)
112
+ perform_action(message, false, @passfail, nil, true, false, &block)
113
+ end
114
+
115
+ # Make a nice status for something that defers any failure code until script exit
116
+ def attempt_multiline(message, &block)
117
+ perform_action(message, true, @passfail, nil, true, false, &block)
118
+ end
119
+
120
+ # Make a nice status for something that kills the script immediately on failure
121
+ FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze
122
+ def assure(message, &block)
123
+ perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
124
+ end
125
+
126
+ def assure_multiline(message, &block)
127
+ perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block)
128
+ end
129
+
130
+ def inform(message, &block)
131
+ perform_action(message, false, proc { |x| x }, nil, false, false, &block)
132
+ end
133
+
134
+ def inform_multiline(message, &block)
135
+ perform_action(message, true, nil, nil, false, false, &block)
136
+ end
137
+
138
+ # Assure that a platform exists and return its definition
139
+ def assured_platform(purpose, name, config)
140
+ platform_definition = config.platform_definition(name)
141
+ assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do
142
+ !platform_definition.nil?
143
+ end
144
+ platform_definition
145
+ end
146
+
147
+ # Return true if the file (or one of the dirs containing it) is hidden
148
+ def file_is_hidden_somewhere?(path)
149
+ # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator
150
+ path.ascend do |part|
151
+ return true if part.basename.to_s.start_with? "."
152
+ end
153
+ false
154
+ end
155
+
156
+ # print out some files
157
+ def display_files(pathname)
158
+ # `find` doesn't follow symlinks, so we should instead
159
+ realpath = pathname.symlink? ? pathname.readlink : pathname
160
+
161
+ # suppress directories and dotfile-based things
162
+ all_files = realpath.find.select(&:file?)
163
+ non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) }
164
+
165
+ # print files with an indent
166
+ margin = " " * FIND_FILES_INDENT
167
+ non_hidden.each { |p| puts "#{margin}#{p}" }
168
+ end
169
+
170
+ def perform_unit_tests(file_config)
171
+ <<<<<<< HEAD
172
+ puts file_config.to_h[:unittest].to_s
173
+ config = file_config.with_override_config(@cli_options)
174
+ puts config.to_h[:unittest].to_s
175
+ =======
176
+ if @cli_options[:skip_unittests]
177
+ inform("Skipping unit tests") { "as requested via command line" }
178
+ return
179
+ end
180
+ config = file_config.with_override_config(@cli_options[:ci_config])
181
+ >>>>>>> 9c4b0f0... Allow skipping of unittests and/or compilation in arduino_ci_remote.rb
182
+ cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."), @arduino_cmd.lib_dir)
183
+
184
+ # check GCC
185
+ compilers = config.compilers_to_use
186
+ assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? }
187
+ compilers.each do |gcc_binary|
188
+ attempt_multiline("Checking #{gcc_binary} version") do
189
+ version = cpp_library.gcc_version(gcc_binary)
190
+ next nil unless version
191
+
192
+ puts version.split("\n").map { |l| " #{l}" }.join("\n")
193
+ version
194
+ end
195
+ inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) }
196
+ end
197
+
198
+ # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name
199
+ all_platform_info = {}
200
+ config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) }
201
+
202
+ # iterate boards / tests
203
+ if !cpp_library.tests_dir.exist?
204
+ inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do
205
+ puts " In case that's an error, this is what was found in the library:"
206
+ display_files(cpp_library.tests_dir.parent)
207
+ true
208
+ end
209
+ elsif cpp_library.test_files.empty?
210
+ inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do
211
+ puts " In case that's an error, this is what was found in the tests directory:"
212
+ display_files(cpp_library.tests_dir)
213
+ true
214
+ end
215
+ elsif config.platforms_to_unittest.empty?
216
+ inform("Skipping unit tests") { "no platforms were requested" }
217
+ else
218
+ config.platforms_to_unittest.each do |p|
219
+ config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path|
220
+ unittest_name = unittest_path.basename.to_s
221
+ compilers.each do |gcc_binary|
222
+ attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do
223
+ exe = cpp_library.build_for_test_with_configuration(
224
+ unittest_path,
225
+ config.aux_libraries_for_unittest,
226
+ gcc_binary,
227
+ config.gcc_config(p)
228
+ )
229
+ puts
230
+ unless exe
231
+ puts "Last command: #{cpp_library.last_cmd}"
232
+ puts cpp_library.last_out
233
+ puts cpp_library.last_err
234
+ next false
235
+ end
236
+ cpp_library.run_test_file(exe)
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ def perform_compilation_tests(config)
245
+ if @cli_options[:skip_compilation]
246
+ inform("Skipping compilation of examples") { "as requested via command line" }
247
+ return
248
+ end
249
+
250
+ # index the existing libraries
251
+ attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed
252
+
253
+ # initialize library under test
254
+ installed_library_path = attempt("Installing library under test") do
255
+ @arduino_cmd.install_local_library(Pathname.new("."))
256
+ end
257
+ if installed_library_path.exist?
258
+ inform("Library installed at") { installed_library_path.to_s }
259
+ else
260
+ assure_multiline("Library installed successfully") do
261
+ # print out the contents of the deepest directory we actually find
262
+ @arduino_cmd.lib_dir.ascend do |path_part|
263
+ next unless path_part.exist?
264
+
265
+ break display_files(path_part)
266
+ end
267
+ false
268
+ end
269
+ end
270
+ library_examples = @arduino_cmd.library_examples(installed_library_path)
271
+
272
+ # gather up all required boards for compilation so we can install them up front.
273
+ # start with the "platforms to unittest" and add the examples
274
+ # while we're doing that, get the aux libraries as well
275
+ example_platform_info = {}
276
+ board_package_url = {}
277
+ aux_libraries = Set.new(config.aux_libraries_for_unittest + config.aux_libraries_for_build)
278
+ # while collecting the platforms, ensure they're defined
279
+
280
+ library_examples.each do |path|
281
+ ovr_config = config.from_example(path)
282
+ ovr_config.platforms_to_build.each do |platform|
283
+ # assure the platform if we haven't already
284
+ next if example_platform_info.key?(platform)
285
+
286
+ platform_info = assured_platform("library example", platform, config)
287
+ next if platform_info.nil?
288
+
289
+ example_platform_info[platform] = platform_info
290
+ package = platform_info[:package]
291
+ board_package_url[package] = ovr_config.package_url(package)
292
+ end
293
+ aux_libraries.merge(ovr_config.aux_libraries_for_build)
294
+ end
295
+
296
+ # with all platform info, we can extract unique packages and their urls
297
+ # do that, set the URLs, and download the packages
298
+ all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?)
299
+
300
+ # inform about builtin packages
301
+ all_packages.select { |p| config.package_builtin?(p) }.each do |p|
302
+ inform("Using built-in board package") { p }
303
+ end
304
+
305
+ # make sure any non-builtin package has a URL defined
306
+ all_packages.reject { |p| config.package_builtin?(p) }.each do |p|
307
+ assure("Board package #{p} has a defined URL") { board_package_url[p] }
308
+ end
309
+
310
+ # set up all the board manager URLs.
311
+ # we can safely reject nils now, they would be for the builtins
312
+ all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?)
313
+
314
+ unless all_urls.empty?
315
+ assure("Setting board manager URLs") do
316
+ @arduino_cmd.board_manager_urls = all_urls
317
+ end
318
+ end
319
+
320
+ all_packages.each do |p|
321
+ assure("Installing board package #{p}") do
322
+ @arduino_cmd.install_boards(p)
323
+ end
324
+ end
325
+
326
+ aux_libraries.each do |l|
327
+ if @arduino_cmd.library_present?(l)
328
+ inform("Using pre-existing library") { l.to_s }
329
+ else
330
+ assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) }
331
+ end
332
+ end
333
+
334
+ last_board = nil
335
+ if config.platforms_to_build.empty?
336
+ inform("Skipping builds") { "no platforms were requested" }
337
+ return
338
+ elsif library_examples.empty?
339
+ inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do
340
+ display_files(installed_library_path)
341
+ end
342
+ return
343
+ end
344
+
345
+ attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") }
346
+
347
+ # switching boards takes time, so iterate board first
348
+ # _then_ whichever examples match it
349
+ examples_by_platform = library_examples.each_with_object({}) do |example_path, acc|
350
+ ovr_config = config.from_example(example_path)
351
+ ovr_config.platforms_to_build.each do |p|
352
+ acc[p] = [] unless acc.key?(p)
353
+ acc[p] << example_path
354
+ end
355
+ end
356
+
357
+ examples_by_platform.each do |platform, example_paths|
358
+ board = example_platform_info[platform][:board]
359
+ assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board
360
+ last_board = board
361
+
362
+ example_paths.each do |example_path|
363
+ example_name = File.basename(example_path)
364
+ attempt("Verifying #{example_name}") do
365
+ ret = @arduino_cmd.verify_sketch(example_path)
366
+ unless ret
367
+ puts
368
+ puts "Last command: #{@arduino_cmd.last_msg}"
369
+ puts @arduino_cmd.last_err
370
+ end
371
+ ret
372
+ end
373
+ end
374
+ end
375
+
376
+ end
377
+
378
+ # initialize command and config
379
+ config = ArduinoCI::CIConfig.default.from_project_library
380
+
381
+ @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!
382
+ inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s }
383
+
384
+ perform_unit_tests(config)
385
+ perform_compilation_tests(config)
386
+
387
+ terminate(true)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arduino_ci'
3
+
4
+ # locate and/or forcibly install Arduino, keep stdout clean
5
+ @arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!($stderr)
6
+
7
+ puts @arduino_cmd.lib_dir
@@ -190,7 +190,10 @@ module ArduinoCI
190
190
  # @param name [String] the library name
191
191
  # @return [bool] whether the command succeeded
192
192
  def _install_library(library_name)
193
- success = run_and_capture(flag_install_library, library_name)[:success]
193
+ result = run_and_capture(flag_install_library, library_name)
194
+
195
+ already_installed = result[:err].include?("Library is already installed: #{library_name}")
196
+ success = result[:success] || already_installed
194
197
 
195
198
  @libraries_indexed = (@libraries_indexed || success) if library_name == WORKAROUND_LIB
196
199
  success
@@ -237,7 +240,7 @@ module ArduinoCI
237
240
  def update_library_index
238
241
  # install random lib so the arduino IDE grabs a new library index
239
242
  # see: https://github.com/arduino/Arduino/issues/3535
240
- install_library("USBHost")
243
+ install_library(WORKAROUND_LIB)
241
244
  end
242
245
 
243
246
  # use a particular board for compilation
@@ -319,7 +322,7 @@ module ArduinoCI
319
322
  proj_file = example_path + e + "#{e}.ino"
320
323
  proj_file.exist? ? proj_file.to_s : nil
321
324
  end
322
- files.reject(&:nil?)
325
+ files.reject(&:nil?).sort_by(&:to_s)
323
326
  end
324
327
  end
325
328
  end
@@ -1,4 +1,5 @@
1
- require "fileutils"
1
+ require 'fileutils'
2
+ require 'net/http'
2
3
  require 'open-uri'
3
4
  require 'zip'
4
5
 
@@ -9,8 +10,11 @@ module ArduinoCI
9
10
  # Manage the OS-specific download & install of Arduino
10
11
  class ArduinoDownloader
11
12
 
12
- def initialize(desired_ide_version)
13
+ # @param desired_ide_version [string] Version string e.g. 1.8.7
14
+ # @param output [IO] $stdout, $stderr, File.new(/dev/null, 'w'), etc. where console output will be sent
15
+ def initialize(desired_ide_version, output = $stdout)
13
16
  @desired_ide_version = desired_ide_version
17
+ @output = output
14
18
  end
15
19
 
16
20
  # Provide guidelines to the implementer of this class
@@ -115,15 +119,15 @@ module ArduinoCI
115
119
  total_size += size
116
120
  needed_dots = (total_size / chunk_size).to_i
117
121
  unprinted_dots = needed_dots - dots
118
- print("." * unprinted_dots) if unprinted_dots > 0
122
+ @output.print("." * unprinted_dots) if unprinted_dots > 0
119
123
  dots = needed_dots
120
124
  end
121
125
 
122
126
  open(package_url, ssl_verify_mode: 0, progress_proc: dot_printer) do |url|
123
127
  File.open(package_file, 'wb') { |file| file.write(url.read) }
124
128
  end
125
- rescue Net::OpenTimeout, Net::ReadTimeout => e
126
- puts "\nArduino force-install failed downloading #{package_url}: #{e}"
129
+ rescue Net::OpenTimeout, Net::ReadTimeout, OpenURI::HTTPError, URI::InvalidURIError => e
130
+ @output.puts "\nArduino force-install failed downloading #{package_url}: #{e}"
127
131
  end
128
132
 
129
133
  # Extract the package_file to extracted_file
@@ -133,7 +137,7 @@ module ArduinoCI
133
137
  batch_size = [1, (zip.size / 100).to_i].max
134
138
  dots = 0
135
139
  zip.each do |file|
136
- print "." if (dots % batch_size).zero?
140
+ @output.print "." if (dots % batch_size).zero?
137
141
  file.restore_permissions = true
138
142
  file.extract { accept_all }
139
143
  dots += 1
@@ -153,7 +157,7 @@ module ArduinoCI
153
157
  def execute
154
158
  error_preparing = prepare
155
159
  unless error_preparing.nil?
156
- puts "Arduino force-install failed preparation: #{error_preparing}"
160
+ @output.puts "Arduino force-install failed preparation: #{error_preparing}"
157
161
  return false
158
162
  end
159
163
 
@@ -161,32 +165,32 @@ module ArduinoCI
161
165
 
162
166
  loop do
163
167
  if File.exist? package_file
164
- puts "Arduino package seems to have been downloaded already" if attempts.zero?
168
+ @output.puts "Arduino package seems to have been downloaded already" if attempts.zero?
165
169
  break
166
170
  elsif attempts >= DOWNLOAD_ATTEMPTS
167
- break puts "After #{DOWNLOAD_ATTEMPTS} attempts, failed to download #{package_url}"
171
+ break @output.puts "After #{DOWNLOAD_ATTEMPTS} attempts, failed to download #{package_url}"
168
172
  else
169
- print "Attempting to download Arduino package with #{downloader}"
173
+ @output.print "Attempting to download Arduino package with #{downloader}"
170
174
  download
171
- puts
175
+ @output.puts
172
176
  end
173
177
  attempts += 1
174
178
  end
175
179
 
176
180
  if File.exist? extracted_file
177
- puts "Arduino package seems to have been extracted already"
181
+ @output.puts "Arduino package seems to have been extracted already"
178
182
  elsif File.exist? package_file
179
- print "Extracting archive with #{extracter}"
183
+ @output.print "Extracting archive with #{extracter}"
180
184
  extract
181
- puts
185
+ @output.puts
182
186
  end
183
187
 
184
188
  if File.exist? self.class.force_install_location
185
- puts "Arduino package seems to have been installed already"
189
+ @output.puts "Arduino package seems to have been installed already"
186
190
  elsif File.exist? extracted_file
187
191
  install
188
192
  else
189
- puts "Could not find extracted archive (tried #{extracted_file})"
193
+ @output.puts "Could not find extracted archive (tried #{extracted_file})"
190
194
  end
191
195
 
192
196
  File.exist? self.class.force_install_location
@@ -2,6 +2,7 @@ require 'base64'
2
2
  require 'shellwords' # fingers crossed this works on win32
3
3
  require 'win32/registry'
4
4
  require "arduino_ci/arduino_downloader"
5
+ require 'net/http'
5
6
  require "fileutils"
6
7
 
7
8
  module ArduinoCI
@@ -30,6 +31,8 @@ module ArduinoCI
30
31
  open(URI.parse(package_url), ssl_verify_mode: 0) do |url|
31
32
  File.open(package_file, 'wb') { |file| file.write(url.read) }
32
33
  end
34
+ rescue Net::OpenTimeout, Net::ReadTimeout, OpenURI::HTTPError, URI::InvalidURIError => e
35
+ @output.puts "\nArduino force-install failed downloading #{package_url}: #{e}"
33
36
  end
34
37
 
35
38
  # Move the extracted package file from extracted_file to the force_install_location
@@ -97,25 +97,25 @@ module ArduinoCI
97
97
 
98
98
  # Attempt to find a workable Arduino executable across platforms, and install it if we don't
99
99
  # @return [ArduinoCI::ArduinoCmd] an instance of a command
100
- def autolocate!
100
+ def autolocate!(output = $stdout)
101
101
  candidate = autolocate
102
102
  return candidate unless candidate.nil?
103
103
 
104
104
  # force the install
105
- raise ArduinoInstallationError, "Failed to force-install Arduino" unless force_install
105
+ raise ArduinoInstallationError, "Failed to force-install Arduino" unless force_install(output)
106
106
 
107
107
  autolocate
108
108
  end
109
109
 
110
110
  # Forcibly install Arduino from the web
111
111
  # @return [bool] Whether the command succeeded
112
- def force_install
112
+ def force_install(output = $stdout, version = DESIRED_ARDUINO_IDE_VERSION)
113
113
  worker_class = case Host.os
114
114
  when :osx then ArduinoDownloaderOSX
115
115
  when :windows then ArduinoDownloaderWindows
116
116
  when :linux then ArduinoDownloaderLinux
117
117
  end
118
- worker = worker_class.new(DESIRED_ARDUINO_IDE_VERSION)
118
+ worker = worker_class.new(version, output)
119
119
  worker.execute
120
120
  end
121
121
 
@@ -32,6 +32,9 @@ module ArduinoCI
32
32
  # @return [String] the last command
33
33
  attr_reader :last_cmd
34
34
 
35
+ # @return [Array<Pathname>] Directories suspected of being vendor-bundle
36
+ attr_reader :vendor_bundle_cache
37
+
35
38
  # @param base_dir [Pathname] The path to the library being tested
36
39
  # @param arduino_lib_dir [Pathname] The path to the libraries directory
37
40
  def initialize(base_dir, arduino_lib_dir)
@@ -45,26 +48,57 @@ module ArduinoCI
45
48
  @last_out = ""
46
49
  @last_msg = ""
47
50
  @has_libasan_cache = {}
51
+ @vendor_bundle_cache = nil
48
52
  end
49
53
 
50
54
  # Guess whether a file is part of the vendor bundle (indicating we should ignore it).
51
55
  #
52
- # This assumes the vendor bundle will be at `vendor/bundle` and not some other location
56
+ # A safe way to do this seems to be to check whether any of the installed gems
57
+ # appear to be a subdirectory of (but not equal to) the working directory.
58
+ # That gets us the vendor directory (or multiple directories). We can check
59
+ # if the given path is contained by any of those.
60
+ #
53
61
  # @param path [Pathname] The path to check
54
62
  # @return [bool]
55
63
  def vendor_bundle?(path)
56
- # TODO: look for Gemfile, look for .bundle/config and get BUNDLE_PATH from there?
57
- base = @base_dir + "vendor"
58
- return false unless base.exist?
64
+ # Cache bundle information, as it is (1) time consuming to fetch and (2) not going to change while we run
65
+ if @vendor_bundle_cache.nil?
66
+ bundle_info = Host.run_and_capture("bundle show --paths")
67
+ if !bundle_info[:success]
68
+ # if the bundle show command fails, assume there isn't a bundle
69
+ @vendor_bundle_cache = false
70
+ else
71
+ # Get all the places where gems are stored. We combine a few things here:
72
+ # by preemptively switching to the parent directory, we can both ensure that
73
+ # we skip any gems that are equal to the working directory AND exploit some
74
+ # commonality in the paths to cut down our search locations
75
+ #
76
+ # NOT CONFUSING THE WORKING DIRECTORY WITH VENDOR BUNDLE IS SUPER IMPORTANT
77
+ # because if we do, we won't be able to run CI on this library itself.
78
+ bundle_paths = bundle_info[:out].lines
79
+ .map { |l| Pathname.new(l.chomp) }
80
+ .select(&:exist?)
81
+ .map(&:realpath)
82
+ .map(&:parent)
83
+ .uniq
84
+ wd = Pathname.new(".").realpath
85
+ @vendor_bundle_cache = bundle_paths.select do |gem_path|
86
+ gem_path.ascend do |part|
87
+ break true if wd == part
88
+ end
89
+ end
90
+ end
91
+ end
59
92
 
60
- vendor_bundle_aliases = [base, base.realpath]
93
+ # no bundle existed
94
+ return false if @vendor_bundle_cache == false
61
95
 
62
- # we could do this but some rubies don't return an enumerator for ascend
63
- # path.ascend.any? { |part| vendor_bundle_aliases.include?(part) }
64
- path.ascend do |part|
65
- return true if vendor_bundle_aliases.include?(part)
96
+ # With vendor bundles located, check this file against those
97
+ @vendor_bundle_cache.any? do |gem_path|
98
+ path.ascend do |part|
99
+ break true if gem_path == part
100
+ end
66
101
  end
67
- false
68
102
  end
69
103
 
70
104
  # Guess whether a file is part of the tests/ dir (indicating library compilation should ignore it).
@@ -109,7 +143,8 @@ module ArduinoCI
109
143
  real = some_dir.realpath
110
144
  files = Find.find(real).map { |p| Pathname.new(p) }.reject(&:directory?)
111
145
  cpp = files.select { |path| CPP_EXTENSIONS.include?(path.extname.downcase) }
112
- cpp.reject { |path| path.basename.to_s.start_with?(".") } # ignore hidden
146
+ not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
147
+ not_hidden.sort_by(&:to_s)
113
148
  end
114
149
 
115
150
  # CPP files that are part of the project library under test
@@ -1,3 +1,3 @@
1
1
  module ArduinoCI
2
- VERSION = "0.1.17".freeze
2
+ VERSION = "0.1.18".freeze
3
3
  end
data/misc/default.yml CHANGED
@@ -98,6 +98,7 @@ platforms:
98
98
  gcc:
99
99
  features:
100
100
  defines:
101
+ - __AVR_ATmega2560__
101
102
  warnings:
102
103
  flags:
103
104
  cplayClassic:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arduino_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Katz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-01-14 00:00:00.000000000 Z
11
+ date: 2019-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: os
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.15'
55
+ - !ruby/object:Gem::Dependency
56
+ name: keepachangelog_manager
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.1
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -99,6 +113,8 @@ email:
99
113
  - ianfixes@gmail.com
100
114
  executables:
101
115
  - arduino_ci_remote.rb
116
+ - arduino_ci_remote.rb.orig
117
+ - arduino_library_location.rb
102
118
  - ensure_arduino_installation.rb
103
119
  - libasan.rb
104
120
  extensions: []
@@ -408,6 +424,8 @@ files:
408
424
  - cpp/unittest/Assertion.h
409
425
  - cpp/unittest/Compare.h
410
426
  - exe/arduino_ci_remote.rb
427
+ - exe/arduino_ci_remote.rb.orig
428
+ - exe/arduino_library_location.rb
411
429
  - exe/ensure_arduino_installation.rb
412
430
  - exe/libasan.rb
413
431
  - lib/arduino_ci.rb