arduino_ci 0.0.1 → 0.1.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.
@@ -0,0 +1,186 @@
1
+ require 'find'
2
+ require "arduino_ci/host"
3
+
4
+ HPP_EXTENSIONS = [".hpp", ".hh", ".h", ".hxx", ".h++"].freeze
5
+ CPP_EXTENSIONS = [".cpp", ".cc", ".c", ".cxx", ".c++"].freeze
6
+ ARDUINO_HEADER_DIR = File.expand_path("../../../cpp/arduino", __FILE__)
7
+ UNITTEST_HEADER_DIR = File.expand_path("../../../cpp/unittest", __FILE__)
8
+
9
+ module ArduinoCI
10
+
11
+ # Information about an Arduino CPP library, specifically for compilation purposes
12
+ class CppLibrary
13
+
14
+ # @return [String] The path to the library being tested
15
+ attr_reader :base_dir
16
+
17
+ # @return [Array<String>] The set of artifacts created by this class (note: incomplete!)
18
+ attr_reader :artifacts
19
+
20
+ # @return [String] STDERR from the last command
21
+ attr_reader :last_err
22
+
23
+ # @return [String] STDOUT from the last command
24
+ attr_reader :last_out
25
+
26
+ # @return [String] the last command
27
+ attr_reader :last_cmd
28
+
29
+ # @param base_dir [String] The path to the library being tested
30
+ def initialize(base_dir)
31
+ @base_dir = File.expand_path(base_dir)
32
+ @artifacts = []
33
+ @last_err = ""
34
+ @last_out = ""
35
+ @last_msg = ""
36
+ end
37
+
38
+ # Get a list of all CPP source files in a directory and its subdirectories
39
+ # @param some_dir [String] The directory in which to begin the search
40
+ # @return [Array<String>] The paths of the found files
41
+ def cpp_files_in(some_dir)
42
+ real = File.realpath(some_dir)
43
+ Find.find(real).select { |path| CPP_EXTENSIONS.include?(File.extname(path)) }
44
+ end
45
+
46
+ # CPP files that are part of the project library under test
47
+ # @return [Array<String>]
48
+ def cpp_files
49
+ real_tests_dir = File.realpath(tests_dir)
50
+ cpp_files_in(@base_dir).reject do |p|
51
+ next true if File.dirname(p).include?(tests_dir)
52
+ next true if File.dirname(p).include?(real_tests_dir)
53
+ end
54
+ end
55
+
56
+ # CPP files that are part of the arduino mock library we're providing
57
+ # @return [Array<String>]
58
+ def cpp_files_arduino
59
+ cpp_files_in(ARDUINO_HEADER_DIR)
60
+ end
61
+
62
+ # CPP files that are part of the unit test library we're providing
63
+ # @return [Array<String>]
64
+ def cpp_files_unittest
65
+ cpp_files_in(UNITTEST_HEADER_DIR)
66
+ end
67
+
68
+ # The directory where we expect to find unit test defintions provided by the user
69
+ # @return [String]
70
+ def tests_dir
71
+ File.join(@base_dir, "test")
72
+ end
73
+
74
+ # The files provided by the user that contain unit tests
75
+ # @return [Array<String>]
76
+ def test_files
77
+ cpp_files_in(tests_dir)
78
+ end
79
+
80
+ # Find all directories in the project library that include C++ header files
81
+ # @return [Array<String>]
82
+ def header_dirs
83
+ files = Find.find(@base_dir).select { |path| HPP_EXTENSIONS.include?(File.extname(path)) }
84
+ files.map { |path| File.dirname(path) }.uniq
85
+ end
86
+
87
+ # wrapper for the GCC command
88
+ def run_gcc(*args, **kwargs)
89
+ pipe_out, pipe_out_wr = IO.pipe
90
+ pipe_err, pipe_err_wr = IO.pipe
91
+ full_args = ["g++"] + args
92
+ @last_cmd = " $ #{full_args.join(' ')}"
93
+ our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
94
+ eventual_kwargs = our_kwargs.merge(kwargs)
95
+ success = Host.run(*full_args, **eventual_kwargs)
96
+
97
+ pipe_out_wr.close
98
+ pipe_err_wr.close
99
+ str_out = pipe_out.read
100
+ str_err = pipe_err.read
101
+ pipe_out.close
102
+ pipe_err.close
103
+ @last_err = str_err
104
+ @last_out = str_out
105
+ success
106
+ end
107
+
108
+ # GCC command line arguments for including aux libraries
109
+ # @param aux_libraries [String] The external Arduino libraries required by this project
110
+ # @return [Array<String>] The GCC command-line flags necessary to include those libraries
111
+ def include_args(aux_libraries)
112
+ places = [ARDUINO_HEADER_DIR, UNITTEST_HEADER_DIR] + header_dirs + aux_libraries
113
+ places.map { |d| "-I#{d}" }
114
+ end
115
+
116
+ # GCC command line arguments for features (e.g. -fno-weak)
117
+ # @param ci_gcc_config [Hash] The GCC config object
118
+ # @return [Array<String>] GCC command-line flags
119
+ def feature_args(ci_gcc_config)
120
+ return [] if ci_gcc_config[:features].nil?
121
+ ci_gcc_config[:features].map { |f| "-f#{f}" }
122
+ end
123
+
124
+ # GCC command line arguments for warning (e.g. -Wall)
125
+ # @param ci_gcc_config [Hash] The GCC config object
126
+ # @return [Array<String>] GCC command-line flags
127
+ def warning_args(ci_gcc_config)
128
+ return [] if ci_gcc_config[:warnings].nil?
129
+ ci_gcc_config[:features].map { |w| "-W#{w}" }
130
+ end
131
+
132
+ # GCC command line arguments for defines (e.g. -Dhave_something)
133
+ # @param ci_gcc_config [Hash] The GCC config object
134
+ # @return [Array<String>] GCC command-line flags
135
+ def define_args(ci_gcc_config)
136
+ return [] if ci_gcc_config[:defines].nil?
137
+ ci_gcc_config[:defines].map { |d| "-D#{d}" }
138
+ end
139
+
140
+ # GCC command line arguments as-is
141
+ # @param ci_gcc_config [Hash] The GCC config object
142
+ # @return [Array<String>] GCC command-line flags
143
+ def flag_args(ci_gcc_config)
144
+ return [] if ci_gcc_config[:flags].nil?
145
+ ci_gcc_config[:flags]
146
+ end
147
+
148
+ # All GCC command line args for building any unit test
149
+ # @param aux_libraries [String] The external Arduino libraries required by this project
150
+ # @param ci_gcc_config [Hash] The GCC config object
151
+ # @return [Array<String>] GCC command-line flags
152
+ def test_args(aux_libraries, ci_gcc_config)
153
+ # TODO: something with libraries?
154
+ ret = include_args(aux_libraries) + cpp_files_arduino + cpp_files_unittest + cpp_files
155
+ unless ci_gcc_config.nil?
156
+ cgc = ci_gcc_config
157
+ ret = feature_args(cgc) + warning_args(cgc) + define_args(cgc) + flag_args(cgc) + ret
158
+ end
159
+ ret
160
+ end
161
+
162
+ # build a file for running a test of the given unit test file
163
+ # @param test_file [String] The path to the file containing the unit tests
164
+ # @param aux_libraries [String] The external Arduino libraries required by this project
165
+ # @param ci_gcc_config [Hash] The GCC config object
166
+ # @return [String] path to the compiled test executable
167
+ def build_for_test_with_configuration(test_file, aux_libraries, ci_gcc_config)
168
+ base = File.basename(test_file)
169
+ executable = File.expand_path("unittest_#{base}.bin")
170
+ File.delete(executable) if File.exist?(executable)
171
+ args = ["-o", executable] + test_args(aux_libraries, ci_gcc_config) + [test_file]
172
+ return nil unless run_gcc(*args)
173
+ artifacts << executable
174
+ executable
175
+ end
176
+
177
+ # run a test file
178
+ # @param [String] the path to the test file
179
+ # @return [bool] whether all tests were successful
180
+ def run_test_file(executable)
181
+ Host.run(executable)
182
+ end
183
+
184
+ end
185
+
186
+ end
@@ -0,0 +1,187 @@
1
+ require 'arduino_ci/host'
2
+ require 'singleton'
3
+ require 'timeout'
4
+
5
+ DESIRED_DISPLAY = ":1.0".freeze
6
+
7
+ module ArduinoCI
8
+
9
+ # When arduino commands run, they need a graphical display.
10
+ # This class handles the setup of that display, if needed.
11
+ class DisplayManager
12
+ include Singleton
13
+
14
+ # @return [bool] whether the display manager is currently active
15
+ attr_reader :enabled
16
+
17
+ # @return [bool] whether to log messages to the terminal
18
+ attr_accessor :debug
19
+
20
+ def initialize
21
+ @existing = existing_display?
22
+ @enabled = false
23
+ @pid = nil
24
+ @debug = false
25
+
26
+ # pipes for input and output
27
+ @xv_pipe_out_wr = nil
28
+ @xv_pipe_err_wr = nil
29
+ @xv_pipe_out = nil
30
+ @xv_pipe_err = nil
31
+ end
32
+
33
+ # attempt to determine if the machine is running a graphical display (i.e. not Travis)
34
+ # @return [bool] whether there is already a GUI that can accept windows
35
+ def existing_display?
36
+ return true if RUBY_PLATFORM.include? "darwin"
37
+ return false if ENV["DISPLAY"].nil?
38
+ return true if ENV["DISPLAY"].include? ":"
39
+ false
40
+ end
41
+
42
+ # check whether a process is alive
43
+ # https://stackoverflow.com/a/32513298/2063546
44
+ # @param pid [Int] the process ID
45
+ # @return [bool]
46
+ def alive?(pid)
47
+ Process.kill(0, pid)
48
+ true
49
+ rescue
50
+ false
51
+ end
52
+
53
+ # check whether an X server is taking connections
54
+ # @param display [String] the display variable as it would be specified in the environment
55
+ # @return [bool]
56
+ def xserver_exist?(display)
57
+ system({ "DISPLAY" => display }, "xdpyinfo", out: File::NULL, err: File::NULL)
58
+ end
59
+
60
+ # wait for the xvfb command to launch
61
+ # @param display [String] the value of the DISPLAY env var
62
+ # @param pid [Int] the process of Xvfb
63
+ # @param timeout [Int] the timeout in seconds
64
+ # @return [Bool] whether we detected a launch
65
+ def xvfb_launched?(display, pid, timeout)
66
+ Timeout.timeout(timeout) do
67
+ loop do
68
+ unless alive? pid
69
+ puts "Xvfb process has died"
70
+ return false
71
+ end
72
+ x = xserver_exist? display
73
+ puts "xdpyinfo reports X server status as #{x}" if debug
74
+ return true if x
75
+ sleep(0.1)
76
+ end
77
+ end
78
+ rescue Timeout::Error
79
+ false
80
+ end
81
+
82
+ # enable a virtual display
83
+ def enable
84
+ if @existing
85
+ puts "DisplayManager enable: no-op for what appears to be an existing display" if debug
86
+ @enabled = true
87
+ return
88
+ end
89
+
90
+ return unless @pid.nil? # TODO: disable first?
91
+ @xv_pipe_out.close unless @xv_pipe_out.nil?
92
+ @xv_pipe_err.close unless @xv_pipe_err.nil?
93
+
94
+ # open Xvfb
95
+ xvfb_cmd = [
96
+ "Xvfb",
97
+ "+extension", "RANDR",
98
+ ":1",
99
+ "-ac",
100
+ "-screen", "0",
101
+ "1280x1024x16",
102
+ ]
103
+ puts "Xvfb launching" if debug
104
+
105
+ @xv_pipe_out, @xv_pipe_out_wr = IO.pipe
106
+ @xv_pipe_err, @xv_pipe_err_wr = IO.pipe
107
+ pipe = IO.popen(xvfb_cmd, stdout: @xv_pipe_out_wr, err: @xv_pipe_err_wr)
108
+ @pid = pipe.pid
109
+ @enabled = xvfb_launched?(DESIRED_DISPLAY, @pid, 30)
110
+ end
111
+
112
+ # disable the virtual display
113
+ def disable
114
+ if @existing
115
+ puts "DisplayManager disable: no-op for what appears to be an existing display" if debug
116
+ return @enabled = false
117
+ end
118
+
119
+ return @enabled = false if @pid.nil?
120
+
121
+ # https://www.whatastruggle.com/timeout-a-subprocess-in-ruby
122
+ begin
123
+ Timeout.timeout(30) do
124
+ Process.kill("TERM", @pid)
125
+ puts "Xvfb TERMed" if debug
126
+ end
127
+ rescue Timeout::Error
128
+ Process.kill(9, @pid)
129
+ puts "Xvfb KILLed" if debug
130
+ ensure
131
+ Process.wait @pid
132
+ @enabled = false
133
+ @pid = nil
134
+
135
+ @xv_pipe_out_wr.close
136
+ @xv_pipe_err_wr.close
137
+ end
138
+ end
139
+
140
+ # Enable a virtual display for the duration of the given block
141
+ # @yield [environment] The code to execute within the display environment
142
+ # @yieldparam [Hash] the environment variables relating to the display
143
+ def with_display
144
+ was_enabled = @enabled
145
+ enable unless was_enabled
146
+ begin
147
+ yield environment
148
+ ensure
149
+ disable unless was_enabled
150
+ end
151
+ end
152
+
153
+ # run a command in a display
154
+ # @return [bool]
155
+ def run(*args, **kwargs)
156
+ ret = false
157
+ # do some work to extract & merge environment variables if they exist
158
+ has_env = !args.empty? && args[0].class == Hash
159
+ with_display do |env_vars|
160
+ env_vars = {} if env_vars.nil?
161
+ env_vars.merge!(args[0]) if has_env
162
+ actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
163
+ full_cmd = env_vars.empty? ? actual_args : [env_vars] + actual_args
164
+ ret = Host.run(*full_cmd, **kwargs)
165
+ end
166
+ ret
167
+ end
168
+
169
+ # run a command in a display with no output
170
+ # @return [bool]
171
+ def run_silent(*args)
172
+ run(*args, out: File::NULL, err: File::NULL)
173
+ end
174
+
175
+ # @return [Hash] the environment variables for the display
176
+ def environment
177
+ return nil unless @existing || @enabled
178
+ return { "EXISTING_DISPLAY" => "YES" } if @existing
179
+ { "DISPLAY" => DESIRED_DISPLAY }
180
+ end
181
+
182
+ # On finalize, ensure child process is ended
183
+ def self.finalize
184
+ disable
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,36 @@
1
+ require 'os'
2
+
3
+ module ArduinoCI
4
+
5
+ # Tools for interacting with the host machine
6
+ class Host
7
+ # Cross-platform way of finding an executable in the $PATH.
8
+ # via https://stackoverflow.com/a/5471032/2063546
9
+ # which('ruby') #=> /usr/bin/ruby
10
+ # @param cmd [String] the command to search for
11
+ # @return [String] the full path to the command if it exists
12
+ def self.which(cmd)
13
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
14
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
15
+ exts.each do |ext|
16
+ exe = File.join(path, "#{cmd}#{ext}")
17
+ return exe if File.executable?(exe) && !File.directory?(exe)
18
+ end
19
+ end
20
+ nil
21
+ end
22
+
23
+ # run a command in a display
24
+ def self.run(*args, **kwargs)
25
+ system(*args, **kwargs)
26
+ end
27
+
28
+ # return [Symbol] the operating system of the host
29
+ def self.os
30
+ return :osx if OS.osx?
31
+ return :linux if OS.linux?
32
+ return :windows if OS.windows?
33
+ end
34
+
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module ArduinoCI
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arduino_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Katz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-10 00:00:00.000000000 Z
11
+ date: 2018-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: os
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,50 +58,49 @@ dependencies:
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '0'
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- version: 0.46.0
61
+ version: 0.49.0
51
62
  type: :development
52
63
  prerelease: false
53
64
  version_requirements: !ruby/object:Gem::Requirement
54
65
  requirements:
55
66
  - - "~>"
56
67
  - !ruby/object:Gem::Version
57
- version: '0'
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: 0.46.0
68
+ version: 0.49.0
61
69
  - !ruby/object:Gem::Dependency
62
70
  name: yard
63
71
  requirement: !ruby/object:Gem::Requirement
64
72
  requirements:
65
73
  - - "~>"
66
74
  - !ruby/object:Gem::Version
67
- version: '0.8'
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- version: '0.8'
75
+ version: 0.9.11
71
76
  type: :development
72
77
  prerelease: false
73
78
  version_requirements: !ruby/object:Gem::Requirement
74
79
  requirements:
75
80
  - - "~>"
76
81
  - !ruby/object:Gem::Version
77
- version: '0.8'
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: '0.8'
82
+ version: 0.9.11
81
83
  description: ''
82
84
  email:
83
85
  - ifreecarve@gmail.com
84
- executables: []
86
+ executables:
87
+ - arduino_ci_remote.rb
85
88
  extensions: []
86
89
  extra_rdoc_files: []
87
90
  files:
88
91
  - ".yardopts"
89
92
  - README.md
93
+ - exe/arduino_ci_remote.rb
90
94
  - lib/arduino_ci.rb
95
+ - lib/arduino_ci/arduino_cmd.rb
96
+ - lib/arduino_ci/arduino_cmd_linux.rb
97
+ - lib/arduino_ci/arduino_cmd_linux_builder.rb
98
+ - lib/arduino_ci/arduino_cmd_osx.rb
99
+ - lib/arduino_ci/arduino_installation.rb
100
+ - lib/arduino_ci/ci_config.rb
101
+ - lib/arduino_ci/cpp_library.rb
102
+ - lib/arduino_ci/display_manager.rb
103
+ - lib/arduino_ci/host.rb
91
104
  - lib/arduino_ci/version.rb
92
105
  homepage: http://github.com/ifreecarve/arduino_ci
93
106
  licenses: