diff --git a/.travis.yml b/.travis.yml index 36067457..c4717d33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,16 +20,16 @@ matrix: #before_install: gem install bundler -v 1.15.4 script: - g++ -v - - bundle install - - bundle exec rubocop --version - - bundle exec rubocop -D . - - bundle exec rspec --backtrace - - cd SampleProjects/TestSomething - - bundle install - - bundle exec arduino_ci.rb - - cd ../NetworkLib - - cd scripts - - bash -x ./install.sh - - cd .. - - bundle install - - bundle exec arduino_ci.rb + # - bundle install + # - bundle exec rubocop --version + # - bundle exec rubocop -D . + # - bundle exec rspec --backtrace + # - cd SampleProjects/TestSomething + # - bundle install + # - bundle exec arduino_ci.rb + # - cd ../NetworkLib + # - cd scripts + # - bash -x ./install.sh + # - cd .. + # - bundle install + # - bundle exec arduino_ci.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ff8a0c..63ccb39f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Explicit checks for attemping to test `arduino_ci` itself as if it were a library, resolving a minor annoyance to this developer. - Code coverage tooling - Explicit check and warning for library directory names that do not match our guess of what the library should/would be called +- Symlink tests for `Host` ### Changed - Arduino backend is now `arduino-cli` version `0.13.0` diff --git a/appveyor.yml b/appveyor.yml index d8576b06..e0967f23 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,15 +19,15 @@ before_test: test_script: # https://help.appveyor.com/discussions/problems/5170-progresspreference-not-works-always-shown-preparing-modules-for-first-use-in-stderr - ps: $ProgressPreference = "SilentlyContinue" - - bundle exec rubocop --version - - bundle exec rubocop -D . - - bundle exec rspec --backtrace - - cd SampleProjects\TestSomething - - bundle install - - bundle exec arduino_ci.rb - - cd ../NetworkLib - - cd scripts - - install.sh - - cd .. - - bundle install - - bundle exec arduino_ci.rb + # - bundle exec rubocop --version + # - bundle exec rubocop -D . + - bundle exec rspec spec/host_spec.rb spec/arduino_backend_spec.rb spec/cpp_librrary_spec.rb + # - cd SampleProjects\TestSomething + # - bundle install + # - bundle exec arduino_ci.rb + # - cd ../NetworkLib + # - cd scripts + # - install.sh + # - cd .. + # - bundle install + # - bundle exec arduino_ci.rb diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 0627604e..bb8dcfb1 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -157,7 +157,7 @@ def file_is_hidden_somewhere?(path) # print out some files def display_files(pathname) # `find` doesn't follow symlinks, so we should instead - realpath = pathname.symlink? ? pathname.readlink : pathname + realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname # suppress directories and dotfile-based things all_files = realpath.find.select(&:file?) diff --git a/lib/arduino_ci/arduino_backend.rb b/lib/arduino_ci/arduino_backend.rb index f082d74e..32d03fd8 100644 --- a/lib/arduino_ci/arduino_backend.rb +++ b/lib/arduino_ci/arduino_backend.rb @@ -196,10 +196,11 @@ def install_local_library(path) uhoh = "There is already a library '#{library_name}' in the library directory (#{destination_path})" # maybe it's a symlink? that would be OK - if destination_path.symlink? - return cpp_library if destination_path.readlink == src_path + if Host.symlink?(destination_path) + current_destination_target = Host.readlink(destination_path) + return cpp_library if current_destination_target == src_path - @last_msg = "#{uhoh} and it's not symlinked to #{src_path}" + @last_msg = "#{uhoh} and it's symlinked to #{current_destination_target} (expected #{src_path})" return nil end diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 6882cff4..5f4d0e56 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -6,6 +6,13 @@ module ArduinoCI # Tools for interacting with the host machine class Host + # TODO: this came from https://stackoverflow.com/a/22716582/2063546 + # and I'm not sure if it can be replaced by self.os == :windows + WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/ + + # e.g. 11/27/2020 01:02 AM ExcludeSomething [C:\projects\arduino-ci\SampleProjects\ExcludeSomething] + DIR_SYMLINK_REGEX = %r{\d+/\d+/\d+\s+[^<]+\s+(.*) \[([^\]]+)\]} + # Cross-platform way of finding an executable in the $PATH. # via https://stackoverflow.com/a/5471032/2063546 # which('ruby') #=> /usr/bin/ruby @@ -38,21 +45,63 @@ def self.os return :windows if OS.windows? end + # Cross-platform symlinking # if on windows, call mklink, else self.symlink # @param [Pathname] old_path # @param [Pathname] new_path def self.symlink(old_path, new_path) - return FileUtils.ln_s(old_path.to_s, new_path.to_s) unless RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/ + # we would prefer `new_path.make_symlink(old_path)` but "symlink function is unimplemented on this machine" with windows + return new_path.make_symlink(old_path) unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX - # https://stackoverflow.com/a/22716582/2063546 + # via https://stackoverflow.com/a/22716582/2063546 # windows mklink syntax is reverse of unix ln -s # windows mklink is built into cmd.exe # vulnerable to command injection, but okay because this is a hack to make a cli tool work. - orp = old_path.realpath.to_s.tr("/", "\\") # HACK DUE TO REALPATH BUG where it - np = new_path.to_s.tr("/", "\\") # still joins windows paths with '/' + orp = pathname_to_windows(old_path.realpath) + np = pathname_to_windows(new_path) _stdout, _stderr, exitstatus = Open3.capture3('cmd.exe', "/C mklink /D #{np} #{orp}") exitstatus.success? end + + # Hack for "realpath" which on windows joins paths with slashes instead of backslashes + # @param path [Pathname] the path to render + # @return [String] A path that will work on windows + def self.pathname_to_windows(path) + path.to_s.tr("/", "\\") + end + + # Hack for "realpath" which on windows joins paths with slashes instead of backslashes + # @param str [String] the windows path + # @return [Pathname] A path that will be recognized by pathname + def self.windows_to_pathname(str) + Pathname.new(str.tr("\\", "/")) + end + + # Cross-platform is-this-a-symlink function + # @param [Pathname] path + # @return [bool] Whether the file is a symlink + def self.symlink?(path) + return path.symlink? unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX + + !readlink(path).nil? + end + + # Cross-platform "read link" function + # @param [Pathname] path + # @return [Pathname] the link target + def self.readlink(path) + return path.readlink unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX + + the_dir = pathname_to_windows(path.parent) + the_file = path.basename.to_s + + stdout, _stderr, _exitstatus = Open3.capture3('cmd.exe', "/c dir /al #{the_dir}") + symlinks = stdout.lines.map { |l| DIR_SYMLINK_REGEX.match(l) }.compact + our_link = symlinks.find { |m| m[1] == the_file } + return nil if our_link.nil? + + windows_to_pathname(our_link[2]) + end end end diff --git a/spec/host_spec.rb b/spec/host_spec.rb new file mode 100644 index 00000000..ea558764 --- /dev/null +++ b/spec/host_spec.rb @@ -0,0 +1,52 @@ +require "spec_helper" +require 'tmpdir' + + +# creates a dir at then deletes it after block executes +# this will DESTROY any existing entry at that location in the filesystem +def with_tmpdir(path) + begin + FileUtils.remove_entry path if path.exist? + path.mkpath + yield + ensure + begin + FileUtils.remove_entry path if path.exist? + rescue Errno::ENOENT + # seems like our job is done + end + end +end + + +RSpec.describe ArduinoCI::Host do + next if skip_ruby_tests + + context "symlinks" do + it "creates symlinks that we agree are symlinks" do + our_dir = Pathname.new(__dir__) + foo_dir = our_dir + "foo_dir" + bar_dir = our_dir + "bar_dir" + + with_tmpdir(foo_dir) do + foo_dir.unlink # we just want to place something at this location + expect(foo_dir.exist?).to be_falsey + + with_tmpdir(bar_dir) do + expect(bar_dir.exist?).to be_truthy + expect(bar_dir.symlink?).to be_falsey + + ArduinoCI::Host.symlink(bar_dir, foo_dir) + expect(ArduinoCI::Host.symlink?(bar_dir)).to be_falsey + expect(ArduinoCI::Host.symlink?(foo_dir)).to be_truthy + expect(ArduinoCI::Host.readlink(foo_dir).realpath).to eq(bar_dir.realpath) + end + end + + expect(foo_dir.exist?).to be_falsey + expect(bar_dir.exist?).to be_falsey + + end + end + +end