Skip to content

Commit 464a21c

Browse files
committed
Add symlink tests
1 parent 3f8d871 commit 464a21c

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- Explicit checks for attemping to test `arduino_ci` itself as if it were a library, resolving a minor annoyance to this developer.
1212
- Code coverage tooling
1313
- Explicit check and warning for library directory names that do not match our guess of what the library should/would be called
14+
- Symlink tests for `Host`
1415

1516
### Changed
1617
- Arduino backend is now `arduino-cli` version `0.13.0`

exe/arduino_ci.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def file_is_hidden_somewhere?(path)
157157
# print out some files
158158
def display_files(pathname)
159159
# `find` doesn't follow symlinks, so we should instead
160-
realpath = pathname.symlink? ? pathname.readlink : pathname
160+
realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname
161161

162162
# suppress directories and dotfile-based things
163163
all_files = realpath.find.select(&:file?)

lib/arduino_ci/arduino_backend.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def install_local_library(path)
196196

197197
uhoh = "There is already a library '#{library_name}' in the library directory (#{destination_path})"
198198
# maybe it's a symlink? that would be OK
199-
if destination_path.symlink?
199+
if Host.symlink?(destination_path)
200200
return cpp_library if destination_path.readlink == src_path
201201

202202
@last_msg = "#{uhoh} and it's not symlinked to #{src_path}"

lib/arduino_ci/host.rb

+39-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ module ArduinoCI
66

77
# Tools for interacting with the host machine
88
class Host
9+
# TODO: this came from https://stackoverflow.com/a/22716582/2063546
10+
# and I'm not sure if it can be replaced by self.os == :windows
11+
WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/.freeze
12+
913
# Cross-platform way of finding an executable in the $PATH.
1014
# via https://stackoverflow.com/a/5471032/2063546
1115
# which('ruby') #=> /usr/bin/ruby
@@ -38,13 +42,15 @@ def self.os
3842
return :windows if OS.windows?
3943
end
4044

45+
# Cross-platform symlinking
4146
# if on windows, call mklink, else self.symlink
4247
# @param [Pathname] old_path
4348
# @param [Pathname] new_path
4449
def self.symlink(old_path, new_path)
45-
return FileUtils.ln_s(old_path.to_s, new_path.to_s) unless RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
50+
# we would prefer `new_path.make_symlink(old_path)` but "symlink function is unimplemented on this machine" with windows
51+
return new_path.make_symlink(old_path) unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
4652

47-
# https://stackoverflow.com/a/22716582/2063546
53+
# via https://stackoverflow.com/a/22716582/2063546
4854
# windows mklink syntax is reverse of unix ln -s
4955
# windows mklink is built into cmd.exe
5056
# vulnerable to command injection, but okay because this is a hack to make a cli tool work.
@@ -54,5 +60,36 @@ def self.symlink(old_path, new_path)
5460
_stdout, _stderr, exitstatus = Open3.capture3('cmd.exe', "/C mklink /D #{np} #{orp}")
5561
exitstatus.success?
5662
end
63+
64+
# Cross-platform is-this-a-symlink function
65+
# @param [Pathname] path
66+
# @return [String] the output of a dir command
67+
def self.get_windows_link_info(path)
68+
# via https://stackoverflow.com/a/22716582/2063546
69+
# vulnerable to command injection, but okay because this is a hack to make a cli tool work.
70+
np = path.to_s.tr("/", "\\") # work around Pathname bug
71+
72+
stdout, _stderr, _exitstatus = Open3.capture3('cmd.exe', "/c dir #{np}")
73+
puts "get_windows_link_info got:\n#{stdout}"
74+
stdout
75+
end
76+
77+
# Cross-platform is-this-a-symlink function
78+
# @param [Pathname] path
79+
# @return [bool] Whether the file is a symlink
80+
def self.symlink?(path)
81+
return path.symlink? unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
82+
83+
get_windows_link_info.include?("SYMLINK")
84+
end
85+
86+
# Cross-platform "read link" function
87+
# @param [Pathname] path
88+
# @return [Pathname] the link target
89+
def self.readlink(path)
90+
return path.readlink unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
91+
92+
get_windows_link_info
93+
end
5794
end
5895
end

spec/host_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require "spec_helper"
2+
3+
RSpec.describe ArduinoCI::Host do
4+
next if skip_ruby_tests
5+
6+
context "symlinks" do
7+
it "creates symlinks that we agree are symlinks" do
8+
Tempfile.create('arduinoci_symlink_test') do |file|
9+
new_path = Pathname.new(file)
10+
new_path.unlink # we just want the guaranteed-unique name, really
11+
12+
expect(new_path.exist?).to be_falsey
13+
ArduinoCI::Host.symlink(__FILE__, new_path)
14+
expect(ArduinoCI::Host.symlink?).to be_truthy
15+
expect(ArduinoCI::Host.readlink.realpath).to eq(Pathname.new(__FILE__).realpath)
16+
end
17+
end
18+
end
19+
20+
end

0 commit comments

Comments
 (0)