diff --git a/Gemfile.lock b/Gemfile.lock index e9c191a..e3cacdb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,12 +2,15 @@ PATH remote: . specs: rush (0.6.8) + encase (~> 0.1.2) session GEM remote: http://rubygems.org/ specs: - fattr (2.2.0) + coderay (1.1.0) + diff-lcs (1.2.5) + encase (0.1.2) git (1.2.5) jeweler (1.8.3) bundler (~> 1.0) @@ -15,18 +18,38 @@ GEM rake rdoc json (1.6.5) + method_source (0.8.2) + pry (0.10.1) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) rake (0.9.2.2) rdoc (3.12) json (~> 1.4) - rspec (1.2.9) - session (3.1.0) - fattr + rspec (3.0.0) + rspec-core (~> 3.0.0) + rspec-expectations (~> 3.0.0) + rspec-mocks (~> 3.0.0) + rspec-core (3.0.4) + rspec-support (~> 3.0.0) + rspec-expectations (3.0.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.0.0) + rspec-mocks (3.0.4) + rspec-support (~> 3.0.0) + rspec-support (3.0.4) + session (3.2.0) + slop (3.6.0) PLATFORMS ruby DEPENDENCIES jeweler (>= 1.8.3) + pry rake (>= 0.9.0) - rspec (~> 1.2.0) + rspec (~> 3.0.0) rush! + +BUNDLED WITH + 1.10.6 diff --git a/README.rdoc b/README.rdoc index a92e8e4..0513ad4 100644 --- a/README.rdoc +++ b/README.rdoc @@ -39,6 +39,8 @@ In rush: Run the "rush" binary to enter the interactive shell. +Upon shell invocation, code will be run from ~/.rush/env.rb, which thus can be thought of as a "rushrc". + == Remote access and clustering rush can control any number of remote machines from a single location. Copy files or directories between servers as seamlessly as if it was all local. diff --git a/Rakefile b/Rakefile index 4cae277..17d157d 100644 --- a/Rakefile +++ b/Rakefile @@ -1,60 +1,38 @@ require 'rake' -require 'jeweler' - -Jeweler::Tasks.new do |s| - s.name = "rush" - s.summary = "A Ruby replacement for bash+ssh." - s.description = "A Ruby replacement for bash+ssh, providing both an interactive shell and a library. Manage both local and remote unix systems from a single client." - s.author = "Adam Wiggins" - s.email = "adam@heroku.com" - s.homepage = "http://rush.heroku.com/" - s.executables = [ "rush", "rushd" ] - s.rubyforge_project = "ruby-shell" - s.has_rdoc = true - - s.add_dependency 'session' - - s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"] -end - -Jeweler::GemcutterTasks.new - -###################################################### - -require 'spec/rake/spectask' +require 'rspec/core/rake_task' desc "Run all specs" -Spec::Rake::SpecTask.new('spec') do |t| - t.spec_files = FileList['spec/*_spec.rb'] +RSpec::Core::RakeTask.new('spec') do |t| + t.pattern = 'spec/*_spec.rb' end desc "Print specdocs" -Spec::Rake::SpecTask.new(:doc) do |t| - t.spec_opts = ["--format", "specdoc", "--dry-run"] - t.spec_files = FileList['spec/*_spec.rb'] +RSpec::Core::RakeTask.new(:doc) do |t| + t.rspec_opts = ["--format", "specdoc", "--dry-run"] + t.pattern = 'spec/*_spec.rb' end desc "Run all examples with RCov" -Spec::Rake::SpecTask.new('rcov') do |t| - t.spec_files = FileList['spec/*_spec.rb'] - t.rcov = true - t.rcov_opts = ['--exclude', 'examples'] +RSpec::Core::RakeTask.new('rcov') do |t| + t.pattern = 'spec/*_spec.rb' + t.rcov = true + t.rcov_opts = ['--exclude', 'examples'] end task :default => :spec ###################################################### -require 'rake/rdoctask' +require 'rdoc/task' Rake::RDocTask.new do |t| - t.rdoc_dir = 'rdoc' - t.title = "rush, a Ruby replacement for bash+ssh" - t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' - t.options << '--charset' << 'utf-8' - t.rdoc_files.include('README.rdoc') - t.rdoc_files.include('lib/rush.rb') - t.rdoc_files.include('lib/rush/*.rb') + t.rdoc_dir = 'rdoc' + t.title = "rush, a Ruby replacement for bash+ssh" + t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + t.options << '--charset' << 'utf-8' + t.rdoc_files.include('README.rdoc') + t.rdoc_files.include('lib/rush.rb') + t.rdoc_files.include('lib/rush/*.rb') end diff --git a/lib/rush.rb b/lib/rush.rb index c192d91..8185cac 100644 --- a/lib/rush.rb +++ b/lib/rush.rb @@ -3,68 +3,69 @@ # The top-level Rush module has some convenience methods for accessing the # local box. module Rush - # Access the root filesystem of the local box. Example: - # - # Rush['/etc/hosts'].contents - # - def self.[](key) - box[key] - end + # Access the root filesystem of the local box. Example: + # + # Rush['/etc/hosts'].contents + # + def self.[](key) + box[key] + end - # Create a dir object from the path of a provided file. Example: - # - # Rush.dir(__FILE__).files - # - def self.dir(filename) - box[::File.expand_path(::File.dirname(filename)) + '/'] - end + # Create a dir object from the path of a provided file. Example: + # + # Rush.dir(__FILE__).files + # + def self.dir(filename) + box[::File.expand_path(::File.dirname(filename)) + '/'] + end - # Create a dir object based on the shell's current working directory at the - # time the program was run. Example: - # - # Rush.launch_dir.files - # - def self.launch_dir - box[::Dir.pwd + '/'] - end + # Create a dir object based on the shell's current working directory at the + # time the program was run. Example: + # + # Rush.launch_dir.files + # + def self.launch_dir + box[::Dir.pwd + '/'] + end - # Run a bash command in the root of the local machine. Equivalent to - # Rush::Box.new.bash. - def self.bash(command, options={}) - box.bash(command, options) - end + # Run a bash command in the root of the local machine. Equivalent to + # Rush::Box.new.bash. + def self.bash(command, options={}) + box.bash(command, options) + end - # Pull the process list for the local machine. Example: + # Pull the process list for the local machine. Example: # # Rush.processes.filter(:cmdline => /ruby/) - # - def self.processes - box.processes - end + # + def self.processes + box.processes + end - # Get the process object for this program's PID. Example: + # Get the process object for this program's PID. Example: # # puts "I'm using #{Rush.my_process.mem} blocks of memory" - # - def self.my_process - box.processes.filter(:pid => ::Process.pid).first - end + # + def self.my_process + box.processes.filter(:pid => ::Process.pid).first + end - # Create a box object for localhost. - def self.box - @@box = Rush::Box.new - end + # Create a box object for localhost. + def self.box + @@box = Rush::Box.new + end - # Quote a path for use in backticks, say. - def self.quote(path) - path.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/n, '\\').gsub(/\n/, "'\n'").sub(/^$/, "''") - end + # Quote a path for use in backticks, say. + def self.quote(path) + path.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/n, '\\').gsub(/\n/, "'\n'").sub(/^$/, "''") + end end module Rush::Connection; end $LOAD_PATH.unshift(File.dirname(__FILE__)) +require 'rush/app' require 'rush/exceptions' require 'rush/config' require 'rush/commands' diff --git a/lib/rush/app.rb b/lib/rush/app.rb new file mode 100644 index 0000000..fb32711 --- /dev/null +++ b/lib/rush/app.rb @@ -0,0 +1,4 @@ +module Rush + class App + end +end diff --git a/lib/rush/shell.rb b/lib/rush/shell.rb index aa74436..868f90a 100644 --- a/lib/rush/shell.rb +++ b/lib/rush/shell.rb @@ -1,187 +1,189 @@ require 'readline' -# Rush::Shell is used to create an interactive shell. It is invoked by the rush binary. +# Rush::Shell is used to create an interactive shell. It is invoked by the rush executable. module Rush - class Shell - attr_accessor :suppress_output - # Set up the user's environment, including a pure binding into which - # env.rb and commands.rb are mixed. - def initialize - root = Rush::Dir.new('/') - home = Rush::Dir.new(ENV['HOME']) if ENV['HOME'] - pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD'] - - @config = Rush::Config.new - - @config.load_history.each do |item| - Readline::HISTORY.push(item) - end - - Readline.basic_word_break_characters = "" - Readline.completion_append_character = nil - Readline.completion_proc = completion_proc - - @box = Rush::Box.new - @pure_binding = @box.instance_eval "binding" - $last_res = nil - - eval @config.load_env, @pure_binding - - commands = @config.load_commands - Rush::Dir.class_eval commands - Array.class_eval commands - end - - # Run a single command. - def execute(cmd) - res = eval(cmd, @pure_binding) - $last_res = res - eval("_ = $last_res", @pure_binding) - print_result res - rescue Rush::Exception => e - puts "Exception #{e.class} -> #{e.message}" - rescue ::Exception => e - puts "Exception #{e.class} -> #{e.message}" - e.backtrace.each do |t| - puts " #{::File.expand_path(t)}" - end - end - - # Run the interactive shell using readline. - def run - loop do - cmd = Readline.readline('rush> ') - - finish if cmd.nil? or cmd == 'exit' - next if cmd == "" - Readline::HISTORY.push(cmd) - - execute(cmd) - end - end - - # Save history to ~/.rush/history when the shell exists. - def finish - @config.save_history(Readline::HISTORY.to_a) - puts - exit - end - - # Nice printing of different return types, particularly Rush::SearchResults. - def print_result(res) - return if self.suppress_output - if res.kind_of? String - puts res - elsif res.kind_of? Rush::SearchResults - widest = res.entries.map { |k| k.full_path.length }.max - res.entries_with_lines.each do |entry, lines| - print entry.full_path - print ' ' * (widest - entry.full_path.length + 2) - print "=> " - print res.colorize(lines.first.strip.head(30)) - print "..." if lines.first.strip.length > 30 - if lines.size > 1 - print " (plus #{lines.size - 1} more matches)" - end - print "\n" - end - puts "#{res.entries.size} matching files with #{res.lines.size} matching lines" - elsif res.respond_to? :each - counts = {} - res.each do |item| - puts item - counts[item.class] ||= 0 - counts[item.class] += 1 - end - if counts == {} - puts "=> (empty set)" - else - count_s = counts.map do |klass, count| - "#{count} x #{klass}" - end.join(', ') - puts "=> #{count_s}" - end - else - puts "=> #{res.inspect}" - end - end - - def path_parts(input) # :nodoc: - case input - when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)([\[\/])(['"])([^\3]*)$/ - $~.to_a.slice(1, 4).push($~.pre_match) - when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)(\.)(\w*)$/ - $~.to_a.slice(1, 3).push($~.pre_match) - when /((?:@{1,2}|\$|)\w+)$/ - $~.to_a.slice(1, 1).push(nil).push($~.pre_match) - else - [ nil, nil, nil ] - end - end - - def complete_method(receiver, dot, partial_name, pre) - path = eval("#{receiver}.full_path", @pure_binding) rescue nil - box = eval("#{receiver}.box", @pure_binding) rescue nil - if path and box - (box[path].methods - Object.methods).select do |e| - e.match(/^#{Regexp.escape(partial_name)}/) - end.map do |e| - (pre || '') + receiver + dot + e - end - end - end - - def complete_path(possible_var, accessor, quote, partial_path, pre) # :nodoc: - original_var, fixed_path = possible_var, '' - if /^(.+\/)([^\/]*)$/ === partial_path - fixed_path, partial_path = $~.captures - possible_var += "['#{fixed_path}']" - end - full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil - box = eval("#{possible_var}.box", @pure_binding) rescue nil - if full_path and box - Rush::Dir.new(full_path, box).entries.select do |e| - e.name.match(/^#{Regexp.escape(partial_path)}/) - end.map do |e| - (pre || '') + original_var + accessor + quote + fixed_path + e.name + (e.dir? ? "/" : "") - end - end - end - - def complete_variable(partial_name, pre) - lvars = eval('local_variables', @pure_binding) - gvars = eval('global_variables', @pure_binding) - ivars = eval('instance_variables', @pure_binding) - (lvars + gvars + ivars).select do |e| - e.match(/^#{Regexp.escape(partial_name)}/) - end.map do |e| - (pre || '') + e - end - end - - # Try to do tab completion on dir square brackets and slash accessors. - # - # Example: - # - # dir['subd # presing tab here will produce dir['subdir/ if subdir exists - # dir/'subd # presing tab here will produce dir/'subdir/ if subdir exists - # - # This isn't that cool yet, because it can't do multiple levels of subdirs. - # It does work remotely, though, which is pretty sweet. - def completion_proc - proc do |input| - receiver, accessor, *rest = path_parts(input) - if receiver - case accessor - when /^[\[\/]$/ - complete_path(receiver, accessor, *rest) - when /^\.$/ - complete_method(receiver, accessor, *rest) - when nil - complete_variable(receiver, *rest) - end - end - end - end - end + class Shell + attr_accessor :suppress_output + # Set up the user's environment, including a pure binding into which + # env.rb and commands.rb are mixed. + def initialize + app = Rush::App.new + + root = Rush::Dir.new('/') + home = Rush::Dir.new(ENV['HOME']) if ENV['HOME'] + pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD'] + + @config = Rush::Config.new + + @config.load_history.each do |item| + Readline::HISTORY.push(item) + end + + Readline.basic_word_break_characters = "" + Readline.completion_append_character = nil + Readline.completion_proc = completion_proc + + @box = Rush::Box.new + @pure_binding = @box.instance_eval "binding" + $last_res = nil + + eval @config.load_env, @pure_binding + + commands = @config.load_commands + Rush::Dir.class_eval commands + Array.class_eval commands + end + + # Run a single command. + def execute(cmd) + res = eval(cmd, @pure_binding) + $last_res = res + eval("_ = $last_res", @pure_binding) + print_result res + rescue Rush::Exception => e + puts "Exception #{e.class} -> #{e.message}" + rescue ::Exception => e + puts "Exception #{e.class} -> #{e.message}" + e.backtrace.each do |t| + puts " #{::File.expand_path(t)}" + end + end + + # Run the interactive shell using readline. + def run + loop do + cmd = Readline.readline('rush> ') + + finish if cmd.nil? or cmd == 'exit' + next if cmd == "" + Readline::HISTORY.push(cmd) + + execute(cmd) + end + end + + # Save history to ~/.rush/history when the shell exists. + def finish + @config.save_history(Readline::HISTORY.to_a) + puts + exit + end + + # Nice printing of different return types, particularly Rush::SearchResults. + def print_result(res) + return if self.suppress_output + if res.kind_of? String + puts res + elsif res.kind_of? Rush::SearchResults + widest = res.entries.map { |k| k.full_path.length }.max + res.entries_with_lines.each do |entry, lines| + print entry.full_path + print ' ' * (widest - entry.full_path.length + 2) + print "=> " + print res.colorize(lines.first.strip.head(30)) + print "..." if lines.first.strip.length > 30 + if lines.size > 1 + print " (plus #{lines.size - 1} more matches)" + end + print "\n" + end + puts "#{res.entries.size} matching files with #{res.lines.size} matching lines" + elsif res.respond_to? :each + counts = {} + res.each do |item| + puts item + counts[item.class] ||= 0 + counts[item.class] += 1 + end + if counts == {} + puts "=> (empty set)" + else + count_s = counts.map do |klass, count| + "#{count} x #{klass}" + end.join(', ') + puts "=> #{count_s}" + end + else + puts "=> #{res.inspect}" + end + end + + def path_parts(input) # :nodoc: + case input + when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)([\[\/])(['"])([^\3]*)$/ + $~.to_a.slice(1, 4).push($~.pre_match) + when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)(\.)(\w*)$/ + $~.to_a.slice(1, 3).push($~.pre_match) + when /((?:@{1,2}|\$|)\w+)$/ + $~.to_a.slice(1, 1).push(nil).push($~.pre_match) + else + [ nil, nil, nil ] + end + end + + def complete_method(receiver, dot, partial_name, pre) + path = eval("#{receiver}.full_path", @pure_binding) rescue nil + box = eval("#{receiver}.box", @pure_binding) rescue nil + if path and box + (box[path].methods - Object.methods).select do |e| + e.match(/^#{Regexp.escape(partial_name)}/) + end.map do |e| + (pre || '') + receiver + dot + e + end + end + end + + def complete_path(possible_var, accessor, quote, partial_path, pre) # :nodoc: + original_var, fixed_path = possible_var, '' + if /^(.+\/)([^\/]*)$/ === partial_path + fixed_path, partial_path = $~.captures + possible_var += "['#{fixed_path}']" + end + full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil + box = eval("#{possible_var}.box", @pure_binding) rescue nil + if full_path and box + Rush::Dir.new(full_path, box).entries.select do |e| + e.name.match(/^#{Regexp.escape(partial_path)}/) + end.map do |e| + (pre || '') + original_var + accessor + quote + fixed_path + e.name + (e.dir? ? "/" : "") + end + end + end + + def complete_variable(partial_name, pre) + lvars = eval('local_variables', @pure_binding) + gvars = eval('global_variables', @pure_binding) + ivars = eval('instance_variables', @pure_binding) + (lvars + gvars + ivars).select do |e| + e.match(/^#{Regexp.escape(partial_name)}/) + end.map do |e| + (pre || '') + e + end + end + + # Try to do tab completion on dir square brackets and slash accessors. + # + # Example: + # + # dir['subd # presing tab here will produce dir['subdir/ if subdir exists + # dir/'subd # presing tab here will produce dir/'subdir/ if subdir exists + # + # This isn't that cool yet, because it can't do multiple levels of subdirs. + # It does work remotely, though, which is pretty sweet. + def completion_proc + proc do |input| + receiver, accessor, *rest = path_parts(input) + if receiver + case accessor + when /^[\[\/]$/ + complete_path(receiver, accessor, *rest) + when /^\.$/ + complete_method(receiver, accessor, *rest) + when nil + complete_variable(receiver, *rest) + end + end + end + end + end end diff --git a/rush.gemspec b/rush.gemspec index b7f081a..934c39a 100644 --- a/rush.gemspec +++ b/rush.gemspec @@ -1,8 +1,3 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command -# -*- encoding: utf-8 -*- - Gem::Specification.new do |s| s.name = %q{rush} s.version = "0.6.8" @@ -99,7 +94,8 @@ Gem::Specification.new do |s| s.add_development_dependency("rake", [">= 0.9.0"]) s.add_development_dependency("jeweler", [">= 1.8.3"]) - s.add_development_dependency("rspec", ["~> 1.2.0"]) + s.add_development_dependency("rspec", ["~> 3.0.0"]) + s.add_development_dependency("pry") if s.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION @@ -113,5 +109,6 @@ Gem::Specification.new do |s| else s.add_dependency(%q, [">= 0"]) end + s.add_dependency "encase", ["~> 0.1.2"] end diff --git a/spec/access_spec.rb b/spec/access_spec.rb index 8e232f7..aeecc56 100644 --- a/spec/access_spec.rb +++ b/spec/access_spec.rb @@ -1,134 +1,135 @@ require File.dirname(__FILE__) + '/base' describe Rush::Access do - before do - @access = Rush::Access.new - end - - it "has roles: user, group, other" do - @access.class.roles == %w(user group other) - end - - it "has permissions: read, write, execute" do - @access.class.permissions == %w(read write execute) - end - - it "gets parts from a one-part symbol like :user" do - @access.parts_from(:user).should == %w(user) - end - - it "gets parts from a two-part symbol like :read_write" do - @access.parts_from(:read_write).should == %w(read write) - end - - it "allows use of 'and' in multipart symbols, like :user_and_group" do - @access.parts_from(:user_and_group).should == %w(user group) - end - - it "extract_list verifies that all the parts among the valid choices" do - @access.should_receive(:parts_from).with(:red_green).and_return(%w(red green)) - @access.extract_list('type', :red_green, %w(red blue green)).should == %w(red green) - end - - it "extract_list raises a BadAccessSpecifier when there is part not in the list of choices" do - lambda do - @access.extract_list('role', :user_bork, %w(user group)) - end.should raise_error(Rush::BadAccessSpecifier, "Unrecognized role: bork") - end - - it "sets one value in the matrix of permissions and roles" do - @access.set_matrix(%w(read), %w(user)) - @access.user_can_read.should == true - end - - it "sets two values in the matrix of permissions and roles" do - @access.set_matrix(%w(read), %w(user group)) - @access.user_can_read.should == true - @access.group_can_read.should == true - end - - it "sets four values in the matrix of permissions and roles" do - @access.set_matrix(%w(read write), %w(user group)) - @access.user_can_read.should == true - @access.group_can_read.should == true - @access.user_can_write.should == true - @access.group_can_write.should == true - end - - it "parse options hash" do - @access.parse(:user_can => :read) - @access.user_can_read.should == true - end - - it "generates octal permissions from its member vars" do - @access.user_can_read = true - @access.octal_permissions.should == 0400 - end - - it "generates octal permissions from its member vars" do - @access.user_can_read = true - @access.user_can_write = true - @access.user_can_execute = true - @access.group_can_read = true - @access.group_can_execute = true - @access.octal_permissions.should == 0750 - end - - it "applies its settings to a file" do - file = "/tmp/rush_spec_#{Process.pid}" - begin - system "rm -rf #{file}; touch #{file}; chmod 770 #{file}" - @access.user_can_read = true - @access.apply(file) - `ls -l #{file}`.should match(/^-r--------/) - ensure - system "rm -rf #{file}; touch #{file}" - end - end - - it "serializes itself to a hash" do - @access.user_can_read = true - @access.to_hash.should == { - :user_can_read => 1, :user_can_write => 0, :user_can_execute => 0, - :group_can_read => 0, :group_can_write => 0, :group_can_execute => 0, - :other_can_read => 0, :other_can_write => 0, :other_can_execute => 0, - } - end - - it "unserializes from a hash" do - @access.from_hash(:user_can_read => '1') - @access.user_can_read.should == true - end - - it "initializes from a serialized hash" do - @access.class.should_receive(:new).and_return(@access) - @access.class.from_hash(:user_can_read => '1').should == @access - @access.user_can_read.should == true - end - - it "initializes from a parsed options hash" do - @access.class.should_receive(:new).and_return(@access) - @access.class.parse(:user_and_group_can => :read).should == @access - @access.user_can_read.should == true - end - - it "converts and octal integer into an array of integers" do - @access.octal_integer_array(0740).should == [ 7, 4, 0 ] - end - - it "filters out anything above the top three digits (File.stat returns some extra data there)" do - @access.octal_integer_array(0100644).should == [ 6, 4, 4 ] - end - - it "taskes permissions from an octal representation" do - @access.from_octal(0644) - @access.user_can_read.should == true - @access.user_can_write.should == true - @access.user_can_execute.should == false - end - - it "computes a display hash by dropping false keys and converting the 1s to trues" do - @access.should_receive(:to_hash).and_return(:red => 1, :green => 0, :blue => 1) - @access.display_hash.should == { :red => true, :blue => true } - end + before do + @access = Rush::Access.new + end + + it "has roles: user, group, other" do + @access.class.roles == %w(user group other) + end + + it "has permissions: read, write, execute" do + @access.class.permissions == %w(read write execute) + end + + it "gets parts from a one-part symbol like :user" do + expect(@access.parts_from(:user)).to eq(%w(user)) + end + + it "gets parts from a two-part symbol like :read_write" do + expect(@access.parts_from(:read_write)).to eq(%w(read write)) + end + + it "allows use of 'and' in multipart symbols, like :user_and_group" do + expect(@access.parts_from(:user_and_group)).to eq(%w(user group)) + end + + it "extract_list verifies that all the parts among the valid choices" do + expect(@access).to receive(:parts_from).with(:red_green).and_return(%w(red green)) + extracted = @access.extract_list('type', :red_green, %w(red blue green)) + expect(extracted).to eq(%w(red green)) + end + + it "extract_list raises a BadAccessSpecifier when there is part not in the list of choices" do + expect(lambda do + @access.extract_list('role', :user_bork, %w(user group)) + end).to raise_error(Rush::BadAccessSpecifier, "Unrecognized role: bork") + end + + it "sets one value in the matrix of permissions and roles" do + @access.set_matrix(%w(read), %w(user)) + expect(@access.user_can_read).to eq(true) + end + + it "sets two values in the matrix of permissions and roles" do + @access.set_matrix(%w(read), %w(user group)) + expect(@access.user_can_read).to eq(true) + expect(@access.group_can_read).to eq(true) + end + + it "sets four values in the matrix of permissions and roles" do + @access.set_matrix(%w(read write), %w(user group)) + expect(@access.user_can_read).to eq(true) + expect(@access.group_can_read).to eq(true) + expect(@access.user_can_write).to eq(true) + expect(@access.group_can_write).to eq(true) + end + + it "parse options hash" do + @access.parse(:user_can => :read) + expect(@access.user_can_read).to eq(true) + end + + it "generates octal permissions from its member vars" do + @access.user_can_read = true + expect(@access.octal_permissions).to eq(0400) + end + + it "generates octal permissions from its member vars" do + @access.user_can_read = true + @access.user_can_write = true + @access.user_can_execute = true + @access.group_can_read = true + @access.group_can_execute = true + expect(@access.octal_permissions).to eq(0750) + end + + it "applies its settings to a file" do + file = "/tmp/rush_spec_#{Process.pid}" + begin + system "rm -rf #{file}; touch #{file}; chmod 770 #{file}" + @access.user_can_read = true + @access.apply(file) + expect(`ls -l #{file}`).to match(/^-r--------/) + ensure + system "rm -rf #{file}; touch #{file}" + end + end + + it "serializes itself to a hash" do + @access.user_can_read = true + expect(@access.to_hash).to eq({ + :user_can_read => 1, :user_can_write => 0, :user_can_execute => 0, + :group_can_read => 0, :group_can_write => 0, :group_can_execute => 0, + :other_can_read => 0, :other_can_write => 0, :other_can_execute => 0, + }) + end + + it "unserializes from a hash" do + @access.from_hash(:user_can_read => '1') + expect(@access.user_can_read).to eq(true) + end + + it "initializes from a serialized hash" do + expect(@access.class).to receive(:new).and_return(@access) + expect(@access.class.from_hash(:user_can_read => '1')).to eq(@access) + expect(@access.user_can_read).to eq(true) + end + + it "initializes from a parsed options hash" do + expect(@access.class).to receive(:new).and_return(@access) + expect(@access.class.parse(:user_and_group_can => :read)).to eq(@access) + expect(@access.user_can_read).to eq(true) + end + + it "converts and octal integer into an array of integers" do + expect(@access.octal_integer_array(0740)).to eq([ 7, 4, 0 ]) + end + + it "filters out anything above the top three digits (File.stat returns some extra data there)" do + expect(@access.octal_integer_array(0100644)).to eq([ 6, 4, 4 ]) + end + + it "taskes permissions from an octal representation" do + @access.from_octal(0644) + expect(@access.user_can_read).to eq(true) + expect(@access.user_can_write).to eq(true) + expect(@access.user_can_execute).to eq(false) + end + + it "computes a display hash by dropping false keys and converting the 1s to trues" do + expect(@access).to receive(:to_hash).and_return(:red => 1, :green => 0, :blue => 1) + expect(@access.display_hash).to eq({ :red => true, :blue => true }) + end end diff --git a/spec/base.rb b/spec/base.rb index 2a0789b..e9769d8 100644 --- a/spec/base.rb +++ b/spec/base.rb @@ -1,24 +1,34 @@ require 'rubygems' -require 'spec' +require 'rspec' + +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = [:should, :expect] + end + + config.mock_with :rspec do |c| + c.syntax = [:should, :expect] + end +end $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') require 'rush' def mock_config(&block) - mock_config_start - block.call(config) - mock_config_end + mock_config_start + block.call(config) + mock_config_end end def mock_config_sandbox_dir - "/tmp/fake_config.#{Process.pid}" + "/tmp/fake_config.#{Process.pid}" end def mock_config_start - mock_config_cleanup - Rush::Config.new(mock_config_sandbox_dir) + mock_config_cleanup + Rush::Config.new(mock_config_sandbox_dir) end def mock_config_cleanup - FileUtils.rm_rf(mock_config_sandbox_dir) + FileUtils.rm_rf(mock_config_sandbox_dir) end diff --git a/spec/box_spec.rb b/spec/box_spec.rb index 7fadb61..2de1d0b 100644 --- a/spec/box_spec.rb +++ b/spec/box_spec.rb @@ -1,76 +1,76 @@ require File.dirname(__FILE__) + '/base' describe Rush::Box do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - - @box = Rush::Box.new('localhost') - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "looks up entries with [] syntax" do - @box['/'].should == Rush::Dir.new('/', @box) - end - - it "looks up processes" do - @box.connection.should_receive(:processes).and_return([ { :pid => 123 } ]) - @box.processes.should == [ Rush::Process.new({ :pid => 123 }, @box) ] - end - - it "executes bash commands" do - @box.connection.should_receive(:bash).with('cmd', nil, false, false).and_return('output') - @box.bash('cmd').should == 'output' - end - - it "executes bash commands with an optional user" do - @box.connection.should_receive(:bash).with('cmd', 'user', false, false) - @box.bash('cmd', :user => 'user') - end - - it "executes bash commands in the background, returning a Rush::Process" do - @box.connection.should_receive(:bash).with('cmd', nil, true, false).and_return(123) - @box.stub!(:processes).and_return([ mock('ps', :pid => 123) ]) - @box.bash('cmd', :background => true).pid.should == 123 - end - - it "builds a script of environment variables to prefix the bash command" do - @box.command_with_environment('cmd', { :a => 'b' }).should == "export a=\"b\"\ncmd" - end - - it "escapes quotes on environment variables" do - @box.command_with_environment('cmd', { :a => 'a"b' }).should == "export a=\"a\\\"b\"\ncmd" - end - - it "escapes backticks on environment variables" do - @box.command_with_environment('cmd', { :a => 'a`b' }).should == "export a=\"a\\\`b\"\ncmd" - end - - it "converts environment variables to_s" do - @box.command_with_environment('cmd', { :a => nil, :b => 123 }).should == "export a=\"\"\nexport b=\"123\"\ncmd" - end - - it "sets the environment variables from the provided hash" do - @box.connection.stub!(:bash) - @box.should_receive(:command_with_environment).with('cmd', { 1 => 2 }) - @box.bash('cmd', :env => { 1 => 2 }) - end - - it "checks the connection to determine if it is alive" do - @box.connection.should_receive(:alive?).and_return(true) - @box.should be_alive - end - - it "establish_connection to set up the connection manually" do - @box.connection.should_receive(:ensure_tunnel) - @box.establish_connection - end - - it "establish_connection can take a hash of options" do - @box.connection.should_receive(:ensure_tunnel).with(:timeout => :infinite) - @box.establish_connection(:timeout => :infinite) - end + before do + @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" + system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" + + @box = Rush::Box.new('localhost') + end + + after do + system "rm -rf #{@sandbox_dir}" + end + + it "looks up entries with [] syntax" do + @box['/'].should == Rush::Dir.new('/', @box) + end + + it "looks up processes" do + @box.connection.should_receive(:processes).and_return([ { :pid => 123 } ]) + @box.processes.should == [ Rush::Process.new({ :pid => 123 }, @box) ] + end + + it "executes bash commands" do + @box.connection.should_receive(:bash).with('cmd', nil, false, false).and_return('output') + @box.bash('cmd').should == 'output' + end + + it "executes bash commands with an optional user" do + @box.connection.should_receive(:bash).with('cmd', 'user', false, false) + @box.bash('cmd', :user => 'user') + end + + it "executes bash commands in the background, returning a Rush::Process" do + expect(@box.connection).to receive(:bash).with('cmd', nil, true, false).and_return(123) + allow(@box).to receive(:processes).and_return([ double('ps', :pid => 123) ]) + @box.bash('cmd', :background => true).pid.should == 123 + end + + it "builds a script of environment variables to prefix the bash command" do + @box.command_with_environment('cmd', { :a => 'b' }).should == "export a=\"b\"\ncmd" + end + + it "escapes quotes on environment variables" do + @box.command_with_environment('cmd', { :a => 'a"b' }).should == "export a=\"a\\\"b\"\ncmd" + end + + it "escapes backticks on environment variables" do + @box.command_with_environment('cmd', { :a => 'a`b' }).should == "export a=\"a\\\`b\"\ncmd" + end + + it "converts environment variables to_s" do + @box.command_with_environment('cmd', { :a => nil, :b => 123 }).should == "export a=\"\"\nexport b=\"123\"\ncmd" + end + + it "sets the environment variables from the provided hash" do + allow(@box.connection).to receive(:bash) + @box.should_receive(:command_with_environment).with('cmd', { 1 => 2 }) + @box.bash('cmd', :env => { 1 => 2 }) + end + + it "checks the connection to determine if it is alive" do + @box.connection.should_receive(:alive?).and_return(true) + @box.should be_alive + end + + it "establish_connection to set up the connection manually" do + @box.connection.should_receive(:ensure_tunnel) + @box.establish_connection + end + + it "establish_connection can take a hash of options" do + @box.connection.should_receive(:ensure_tunnel).with(:timeout => :infinite) + @box.establish_connection(:timeout => :infinite) + end end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 301e93e..d5b0cdb 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -1,108 +1,108 @@ require File.dirname(__FILE__) + '/base' describe Rush::Config do - before do - @sandbox_dir = "/tmp/rush_config_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}" - @config = Rush::Config.new(@sandbox_dir) - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "creates the dir" do - File.directory?(@sandbox_dir).should be_true - end - - it "can access the history file" do - @config.history_file.class.should == Rush::File - end - - it "saves the shell command history" do - @config.save_history(%w(1 2 3)) - @config.history_file.contents.should == "1\n2\n3\n" - end - - it "loads the shell command history" do - @config.save_history(%w(1 2 3)) - @config.load_history.should == %w(1 2 3) - end - - it "loads a blank history if no history file" do - @config.load_history.should == [] - end - - it "loads the env file" do - @config.env_file.write('abc') - @config.load_env.should == 'abc' - end - - it "loads nothing if env file does not exist" do - @config.load_env.should == "" - end - - it "loads the commands file" do - @config.commands_file.write('abc') - @config.load_commands.should == 'abc' - end - - it "loads nothing if commands file does not exist" do - @config.load_commands.should == "" - end - - it "loads usernames and password for rushd" do - system "echo 1:2 > #{@sandbox_dir}/passwords" - system "echo a:b >> #{@sandbox_dir}/passwords" - @config.passwords.should == { '1' => '2', 'a' => 'b' } - end - - it "loads blank hash if no passwords file" do - @config.passwords.should == { } - end - - it "loads credentials for client connecting to server" do - system "echo user:pass > #{@sandbox_dir}/credentials" - @config.credentials_user.should == 'user' - @config.credentials_password.should == 'pass' - end - - it "loads list of ssh tunnels" do - system "echo host.example.com:123 > #{@sandbox_dir}/tunnels" - @config.tunnels.should == { 'host.example.com' => 123 } - end - - it "returns an empty hash if tunnels file is blank" do - @config.tunnels.should == { } - end - - it "saves a list of ssh tunnels" do - @config.save_tunnels({ 'my.example.com' => 4000 }) - @config.tunnels_file.contents.should == "my.example.com:4000\n" - end - - it "ensure_credentials_exist doesn't do anything if credentials already exist" do - @config.credentials_file.write "dummy" - @config.should_receive(:generate_credentials).exactly(0).times - @config.ensure_credentials_exist - end - - it "ensure_credentials_exist generates credentials file if they don't exist" do - @config.should_receive(:generate_credentials) - @config.ensure_credentials_exist - end - - it "secret_characters returns valid characters for username or password" do - @config.secret_characters.should be_kind_of(Array) - end - - it "generate_secret products a random string for use in username and password" do - @config.should_receive(:secret_characters).and_return(%w(a)) - @config.generate_secret(2, 2).should == "aa" - end - - it "generate_credentials saves credentials" do - @config.generate_credentials - @config.credentials_file.contents.should match(/^.+:.+$/) - end + before do + @sandbox_dir = "/tmp/rush_config_spec.#{Process.pid}" + system "rm -rf #{@sandbox_dir}" + @config = Rush::Config.new(@sandbox_dir) + end + + after do + system "rm -rf #{@sandbox_dir}" + end + + it "creates the dir" do + File.directory?(@sandbox_dir).should eq(true) + end + + it "can access the history file" do + @config.history_file.class.should == Rush::File + end + + it "saves the shell command history" do + @config.save_history(%w(1 2 3)) + @config.history_file.contents.should == "1\n2\n3\n" + end + + it "loads the shell command history" do + @config.save_history(%w(1 2 3)) + @config.load_history.should == %w(1 2 3) + end + + it "loads a blank history if no history file" do + @config.load_history.should == [] + end + + it "loads the env file" do + @config.env_file.write('abc') + @config.load_env.should == 'abc' + end + + it "loads nothing if env file does not exist" do + @config.load_env.should == "" + end + + it "loads the commands file" do + @config.commands_file.write('abc') + @config.load_commands.should == 'abc' + end + + it "loads nothing if commands file does not exist" do + @config.load_commands.should == "" + end + + it "loads usernames and password for rushd" do + system "echo 1:2 > #{@sandbox_dir}/passwords" + system "echo a:b >> #{@sandbox_dir}/passwords" + @config.passwords.should == { '1' => '2', 'a' => 'b' } + end + + it "loads blank hash if no passwords file" do + @config.passwords.should == { } + end + + it "loads credentials for client connecting to server" do + system "echo user:pass > #{@sandbox_dir}/credentials" + @config.credentials_user.should == 'user' + @config.credentials_password.should == 'pass' + end + + it "loads list of ssh tunnels" do + system "echo host.example.com:123 > #{@sandbox_dir}/tunnels" + @config.tunnels.should == { 'host.example.com' => 123 } + end + + it "returns an empty hash if tunnels file is blank" do + @config.tunnels.should == { } + end + + it "saves a list of ssh tunnels" do + @config.save_tunnels({ 'my.example.com' => 4000 }) + @config.tunnels_file.contents.should == "my.example.com:4000\n" + end + + it "ensure_credentials_exist doesn't do anything if credentials already exist" do + @config.credentials_file.write "dummy" + @config.should_receive(:generate_credentials).exactly(0).times + @config.ensure_credentials_exist + end + + it "ensure_credentials_exist generates credentials file if they don't exist" do + @config.should_receive(:generate_credentials) + @config.ensure_credentials_exist + end + + it "secret_characters returns valid characters for username or password" do + @config.secret_characters.should be_kind_of(Array) + end + + it "generate_secret products a random string for use in username and password" do + @config.should_receive(:secret_characters).and_return(%w(a)) + @config.generate_secret(2, 2).should == "aa" + end + + it "generate_credentials saves credentials" do + @config.generate_credentials + @config.credentials_file.contents.should match(/^.+:.+$/) + end end diff --git a/spec/dir_spec.rb b/spec/dir_spec.rb index 19324e8..b2b0a8d 100644 --- a/spec/dir_spec.rb +++ b/spec/dir_spec.rb @@ -1,164 +1,165 @@ require File.dirname(__FILE__) + '/base' describe Rush::Dir do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - - @dirname = "#{@sandbox_dir}/test_dir/" - system "mkdir -p #{@dirname}" - - @dir = Rush::Dir.new(@dirname) - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "is a child of Rush::Entry" do - @dir.should be_kind_of(Rush::Entry) - end - - it "can create itself, returning itself" do - system "rm -rf #{@sandbox_dir}" - @dir.create.should == @dir - File.directory?(@dir.full_path).should be_true - end - - it "can create a new file" do - newfile = @dir.create_file('one.txt') - newfile.name.should == 'one.txt' - newfile.parent.should == @dir - end - - it "can create a new subdir" do - newfile = @dir['two/'].create - newfile.name.should == 'two' - newfile.parent.should == @dir - end - - it "find_by_name finds a single entry in the contents" do - file = @dir.create_file('one.rb') - @dir.find_by_name('one.rb').should == file - end - - it "find_by_glob finds a list of entries by wildcard" do - file1 = @dir.create_file('one.rb') - file2 = @dir.create_file('two.txt') - @dir.find_by_glob('*.rb').should == [ file1 ] - end - - it "lists files" do - @dir.create_file('a') - @dir.files.should == [ Rush::File.new("#{@dirname}/a") ] - end - - it "lists dirs" do - system "mkdir -p #{@dir.full_path}/b" - @dir.dirs.should == [ Rush::Dir.new("#{@dirname}/b") ] - end - - it "lists combined files and dirs" do - @dir['c'].create - @dir['d/'].create - @dir.contents.size.should == 2 - end - - it "fetches the entry_tree of all contents recursively" do - @dir['a/'].create['b/'].create['c'].create - @dir.entries_tree.should == @dir.make_entries(%w(a/ a/b/ a/b/c)) - end - - it "maps [] to find_by_name" do - @dir.should_receive(:find_by_name).once - @dir['one'] - end - - it "maps [] with a wildcard character to find_by_glob" do - @dir.should_receive(:find_by_glob).once - @dir['*'] - end - - it "can use symbols or strings for [] access" do - @dir.should_receive(:find_by_name).once.with('subdir') - @dir[:subdir] - end - - it "[] can return a file that has yet to be created" do - @dir['not_yet'].class.should == Rush::File - end - - it "makes a list of entries from an array of filenames" do - @dir['a'].create - @dir['b/c/'].create - @dir.make_entries(%w(a b/c)).should == [ @dir['a'], @dir['b/c'] ] - end - - it "lists flattened files from all nested subdirectories" do - @dir['1'].create - @dir['2/3/'].create['4'].create - @dir['a/b/c/'].create['d'].create - @dir.files_flattened.should == @dir.make_entries(%w(1 2/3/4 a/b/c/d)) - end - - it "lists flattened dirs from all nested subdirectories" do - @dir.create_dir('1/2') - @dir.dirs_flattened.should == @dir.make_entries(%w(1/ 1/2/)) - end - - it "** as a shortcut to flattened_files" do - @dir['**'].should == @dir.files_flattened - end - - it "**/ as a shortcut to flattened_files + regular globbing" do - @dir.create_file('1.rb') - @dir.create_file('ignore.txt') - @dir.create_dir('2').create_file('3.rb') - @dir.create_dir('a/b').create_file('c.rb') - @dir['**/*.rb'].should == @dir.make_entries(%w(1.rb 2/3.rb a/b/c.rb)) - end - - it "lists nonhidden files" do - @dir.create_file('show') - @dir.create_file('.dont_show') - @dir.nonhidden_files.should == @dir.make_entries(%(show)) - end - - it "lists nonhidden dirs" do - @dir.create_dir('show') - @dir.create_dir('.dont_show') - @dir.nonhidden_dirs.should == @dir.make_entries(%(show/)) - end - - if !RUBY_PLATFORM.match(/darwin/) # doesn't work on OS X 'cause du switches are different - it "knows its size in bytes, which includes its contents recursively" do - @dir.create_file('a').write('1234') - @dir.size.should be(4096 + 4) - end - end - - it "can destroy itself when empty" do - @dir.destroy - end - - it "can destroy itself when not empty" do - @dir.create_dir('a').create_file('b').write('c') - @dir.destroy - end - - it "can run a bash command within itself" do - system "echo test > #{@dir.full_path}/file" - @dir.bash("cat file").should == "test\n" - end - - it "can run bash within directories with spaces" do - @dir.create_dir('with space').create_file('file').write('test') - @dir["with space/"].bash("cat file").should == "test" - end - - it "passes bash options (e.g., :user) through to the box bash command" do - @dir.should_receive(:bash).with('cmd', :opt1 => 1, :opt2 => 2) - @dir.bash('cmd', :opt1 => 1, :opt2 => 2) - end + before do + @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" + system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" + + @dirname = "#{@sandbox_dir}/test_dir/" + system "mkdir -p #{@dirname}" + + @dir = Rush::Dir.new(@dirname) + end + + after do + system "rm -rf #{@sandbox_dir}" + end + + it "is a child of Rush::Entry" do + @dir.should be_kind_of(Rush::Entry) + end + + it "can create itself, returning itself" do + system "rm -rf #{@sandbox_dir}" + @dir.create.should == @dir + File.directory?(@dir.full_path).should eq(true) + end + + it "can create a new file" do + newfile = @dir.create_file('one.txt') + newfile.name.should == 'one.txt' + newfile.parent.should == @dir + end + + it "can create a new subdir" do + newfile = @dir['two/'].create + newfile.name.should == 'two' + newfile.parent.should == @dir + end + + it "find_by_name finds a single entry in the contents" do + file = @dir.create_file('one.rb') + @dir.find_by_name('one.rb').should == file + end + + it "find_by_glob finds a list of entries by wildcard" do + file1 = @dir.create_file('one.rb') + file2 = @dir.create_file('two.txt') + @dir.find_by_glob('*.rb').should == [ file1 ] + end + + it "lists files" do + @dir.create_file('a') + @dir.files.should == [ Rush::File.new("#{@dirname}/a") ] + end + + it "lists dirs" do + system "mkdir -p #{@dir.full_path}/b" + @dir.dirs.should == [ Rush::Dir.new("#{@dirname}/b") ] + end + + it "lists combined files and dirs" do + @dir['c'].create + @dir['d/'].create + @dir.contents.size.should == 2 + end + + it "fetches the entry_tree of all contents recursively" do + @dir['a/'].create['b/'].create['c'].create + @dir.entries_tree.should == @dir.make_entries(%w(a/ a/b/ a/b/c)) + end + + it "maps [] to find_by_name" do + @dir.should_receive(:find_by_name).once + @dir['one'] + end + + it "maps [] with a wildcard character to find_by_glob" do + @dir.should_receive(:find_by_glob).once + @dir['*'] + end + + it "can use symbols or strings for [] access" do + @dir.should_receive(:find_by_name).once.with('subdir') + @dir[:subdir] + end + + it "[] can return a file that has yet to be created" do + @dir['not_yet'].class.should == Rush::File + end + + it "makes a list of entries from an array of filenames" do + @dir['a'].create + @dir['b/c/'].create + @dir.make_entries(%w(a b/c)).should == [ @dir['a'], @dir['b/c'] ] + end + + it "lists flattened files from all nested subdirectories" do + @dir['1'].create + @dir['2/3/'].create['4'].create + @dir['a/b/c/'].create['d'].create + @dir.files_flattened.should == @dir.make_entries(%w(1 2/3/4 a/b/c/d)) + end + + it "lists flattened dirs from all nested subdirectories" do + @dir.create_dir('1/2') + @dir.dirs_flattened.should == @dir.make_entries(%w(1/ 1/2/)) + end + + it "** as a shortcut to flattened_files" do + @dir['**'].should == @dir.files_flattened + end + + it "**/ as a shortcut to flattened_files + regular globbing" do + @dir.create_file('1.rb') + @dir.create_file('ignore.txt') + @dir.create_dir('2').create_file('3.rb') + @dir.create_dir('a/b').create_file('c.rb') + @dir['**/*.rb'].should == @dir.make_entries(%w(1.rb 2/3.rb a/b/c.rb)) + end + + it "lists nonhidden files" do + @dir.create_file('show') + @dir.create_file('.dont_show') + + @dir.nonhidden_files.should == @dir.make_entries(%w(show)) + end + + it "lists nonhidden dirs" do + @dir.create_dir('show') + @dir.create_dir('.dont_show') + @dir.nonhidden_dirs.should == @dir.make_entries(%w(show/)) + end + + if !RUBY_PLATFORM.match(/darwin/) # doesn't work on OS X 'cause du switches are different + it "knows its size in bytes, which includes its contents recursively" do + @dir.create_file('a').write('1234') + @dir.size.should be(4096 + 4) + end + end + + it "can destroy itself when empty" do + @dir.destroy + end + + it "can destroy itself when not empty" do + @dir.create_dir('a').create_file('b').write('c') + @dir.destroy + end + + it "can run a bash command within itself" do + system "echo test > #{@dir.full_path}/file" + @dir.bash("cat file").should == "test\n" + end + + it "can run bash within directories with spaces" do + @dir.create_dir('with space').create_file('file').write('test') + @dir["with space/"].bash("cat file").should == "test" + end + + it "passes bash options (e.g., :user) through to the box bash command" do + @dir.should_receive(:bash).with('cmd', :opt1 => 1, :opt2 => 2) + @dir.bash('cmd', :opt1 => 1, :opt2 => 2) + end end diff --git a/spec/entry_spec.rb b/spec/entry_spec.rb index f018b8f..65e54b8 100644 --- a/spec/entry_spec.rb +++ b/spec/entry_spec.rb @@ -1,133 +1,133 @@ require File.dirname(__FILE__) + '/base' describe Rush::Entry do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}/" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" + before do + @sandbox_dir = "/tmp/rush_spec.#{Process.pid}/" + system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - @filename = "#{@sandbox_dir}/test_file" - system "touch #{@filename}" + @filename = "#{@sandbox_dir}/test_file" + system "touch #{@filename}" - @entry = Rush::Entry.new(@filename) - end + @entry = Rush::Entry.new(@filename) + end - after do - system "rm -rf #{@sandbox_dir}" - end + after do + system "rm -rf #{@sandbox_dir}" + end - it "knows its name" do - @entry.name.should == File.basename(@filename) - end + it "knows its name" do + @entry.name.should == File.basename(@filename) + end - it "knows its parent dir" do - @entry.parent.should be_kind_of(Rush::Dir) - @entry.parent.name.should == File.basename(@sandbox_dir) - @entry.parent.full_path.should == @sandbox_dir - end - - it "cleans its pathname" do - Rush::Entry.new('/a//b//c').full_path.should == '/a/b/c' - Rush::Entry.new('/1/2/../3').full_path.should == '/1/3' - end - - it "knows its changed_at time" do - @entry.changed_at.should == File.stat(@filename).ctime - end - - it "knows its last_modified time" do - @entry.last_modified.should == File.stat(@filename).mtime - end - - it "knows its last_accessed time" do - @entry.last_accessed.should == File.stat(@filename).atime - end - - it "considers itself equal to other instances with the same full path" do - Rush::Entry.new('/not/the/same').should_not == @entry - Rush::Entry.new(@entry.full_path).should == @entry - end - - it "can rename itself" do - new_file = "test2" - - @entry.rename(new_file) - - File.exists?(@filename).should be_false - File.exists?("#{@sandbox_dir}/#{new_file}").should be_true - end - - it "rename returns the renamed file" do - @entry.rename('file2').should == @entry.parent['file2'] - end - - it "can't rename itself if another file already exists with that name" do - new_file = "test3" - system "touch #{@sandbox_dir}/#{new_file}" - - lambda { @entry.rename(new_file) }.should raise_error(Rush::NameAlreadyExists, /#{new_file}/) - end - - it "can't rename itself to something with a slash in it" do - lambda { @entry.rename('has/slash') }.should raise_error(Rush::NameCannotContainSlash, /slash/) - end - - it "can duplicate itself within the directory" do - @entry.duplicate('newfile').should == Rush::File.new("#{@sandbox_dir}/newfile") - end - - it "can move itself to another dir" do - newdir = "#{@sandbox_dir}/newdir" - system "mkdir -p #{newdir}" - - dst = Rush::Dir.new(newdir) - @entry.move_to(dst) - - File.exists?(@filename).should be_false - File.exists?("#{newdir}/#{@entry.name}").should be_true - end - - it "can copy itself to another directory" do - newdir = "#{@sandbox_dir}/newdir" - system "mkdir -p #{newdir}" - - dst = Rush::Dir.new(newdir) - @copied_dir = @entry.copy_to(dst) - - File.exists?(@filename).should be_true - File.exists?("#{newdir}/#{@entry.name}").should be_true - - @copied_dir.full_path.should == "#{@sandbox_dir}newdir/#{@entry.name}" - end - - it "considers dotfiles to be hidden" do - Rush::Entry.new("#{@sandbox_dir}/show").should_not be_hidden - Rush::Entry.new("#{@sandbox_dir}/.dont_show").should be_hidden - end + it "knows its parent dir" do + @entry.parent.should be_kind_of(Rush::Dir) + @entry.parent.name.should == File.basename(@sandbox_dir) + @entry.parent.full_path.should == @sandbox_dir + end + + it "cleans its pathname" do + Rush::Entry.new('/a//b//c').full_path.should == '/a/b/c' + Rush::Entry.new('/1/2/../3').full_path.should == '/1/3' + end + + it "knows its changed_at time" do + @entry.changed_at.should == File.stat(@filename).ctime + end + + it "knows its last_modified time" do + @entry.last_modified.should == File.stat(@filename).mtime + end + + it "knows its last_accessed time" do + @entry.last_accessed.should == File.stat(@filename).atime + end + + it "considers itself equal to other instances with the same full path" do + Rush::Entry.new('/not/the/same').should_not == @entry + Rush::Entry.new(@entry.full_path).should == @entry + end + + it "can rename itself" do + new_file = "test2" + + @entry.rename(new_file) + + File.exists?(@filename).should eq(false) + File.exists?("#{@sandbox_dir}/#{new_file}").should eq(true) + end + + it "rename returns the renamed file" do + @entry.rename('file2').should == @entry.parent['file2'] + end + + it "can't rename itself if another file already exists with that name" do + new_file = "test3" + system "touch #{@sandbox_dir}/#{new_file}" + + lambda { @entry.rename(new_file) }.should raise_error(Rush::NameAlreadyExists, /#{new_file}/) + end + + it "can't rename itself to something with a slash in it" do + lambda { @entry.rename('has/slash') }.should raise_error(Rush::NameCannotContainSlash, /slash/) + end + + it "can duplicate itself within the directory" do + @entry.duplicate('newfile').should == Rush::File.new("#{@sandbox_dir}/newfile") + end + + it "can move itself to another dir" do + newdir = "#{@sandbox_dir}/newdir" + system "mkdir -p #{newdir}" + + dst = Rush::Dir.new(newdir) + @entry.move_to(dst) + + File.exists?(@filename).should eq(false) + File.exists?("#{newdir}/#{@entry.name}").should eq(true) + end + + it "can copy itself to another directory" do + newdir = "#{@sandbox_dir}/newdir" + system "mkdir -p #{newdir}" + + dst = Rush::Dir.new(newdir) + @copied_dir = @entry.copy_to(dst) + + File.exists?(@filename).should eq(true) + File.exists?("#{newdir}/#{@entry.name}").should eq(true) + + @copied_dir.full_path.should == "#{@sandbox_dir}newdir/#{@entry.name}" + end + + it "considers dotfiles to be hidden" do + Rush::Entry.new("#{@sandbox_dir}/show").should_not be_hidden + Rush::Entry.new("#{@sandbox_dir}/.dont_show").should be_hidden + end - it "is considered equal to entries with the same full path and on the same box" do - same = Rush::Entry.new(@entry.full_path, @entry.box) - @entry.should == same - end - - it "is considered not equal to entries with the same full path on a different box" do - same = Rush::Entry.new(@entry.full_path, Rush::Box.new('dummy')) - @entry.should_not == same - end - - it "can mimic another entry" do - copy = Rush::Entry.new('abc', :dummy) - copy.mimic(@entry) - copy.path.should == @entry.path - end - - it "can update the read access permission" do - system "chmod 666 #{@filename}" - @entry.access = { :user_can => :read } - `ls -l #{@filename}`.should match(/^-r--------/) - end - - it "reads the file permissions in the access hash" do - system "chmod 640 #{@filename}" - @entry.access.should == { :user_can_read => true, :user_can_write => true, :group_can_read => true } - end + it "is considered equal to entries with the same full path and on the same box" do + same = Rush::Entry.new(@entry.full_path, @entry.box) + @entry.should == same + end + + it "is considered not equal to entries with the same full path on a different box" do + same = Rush::Entry.new(@entry.full_path, Rush::Box.new('dummy')) + @entry.should_not == same + end + + it "can mimic another entry" do + copy = Rush::Entry.new('abc', :dummy) + copy.mimic(@entry) + copy.path.should == @entry.path + end + + it "can update the read access permission" do + system "chmod 666 #{@filename}" + @entry.access = { :user_can => :read } + `ls -l #{@filename}`.should match(/^-r--------/) + end + + it "reads the file permissions in the access hash" do + system "chmod 640 #{@filename}" + @entry.access.should == { :user_can_read => true, :user_can_write => true, :group_can_read => true } + end end diff --git a/spec/file_spec.rb b/spec/file_spec.rb index de491f1..136676a 100644 --- a/spec/file_spec.rb +++ b/spec/file_spec.rb @@ -1,83 +1,83 @@ require File.dirname(__FILE__) + '/base' describe Rush::File do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - - @filename = "#{@sandbox_dir}/test_file" - @contents = "1234" - system "echo #{@contents} > #{@filename}" - @contents += "\n" - - @file = Rush::File.new(@filename) - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "is a child of Rush::Entry" do - @file.should be_kind_of(Rush::Entry) - end - - it "is not a dir" do - @file.should_not be_dir - end - - it "can create itself as a blank file, and return itself" do - create_me = Rush::File.new("#{@sandbox_dir}/create_me") - create_me.create.should == create_me - File.exists?("#{@sandbox_dir}/create_me").should == true - end - - it "knows its size in bytes" do - @file.size.should == @contents.length - end - - it "can read its contents" do - @file.contents.should == @contents - end - - it "read is an alias for contents" do - @file.read.should == @contents - end - - it "can write new contents" do - @file.write('write test') - @file.contents.should == 'write test' - end - - it "can count the number of lines it contains" do - @file.write("1\n2\n3\n") - @file.line_count.should == 3 - end - - it "searches its contents for matching lines" do - @file.write("a\n1\nb\n2\n") - @file.search(/\d/).should == %w(1 2) - end - - it "search returns nil if no lines match" do - @file.write("a\nb\nc\n") - @file.search(/\d/).should be_nil - end - - it "find-in-file replace" do - @file.replace_contents!(/\d/, 'x') - @file.contents.should == "xxxx\n" - end - - it "can destroy itself" do - @file.destroy - ::File.exists?(@filename).should be_false - end - - it "can fetch contents or blank if doesn't exist" do - Rush::File.new('/does/not/exist').contents_or_blank.should == "" - end - - it "can fetch lines, or empty if doesn't exist" do - Rush::File.new('/does/not/exist').lines_or_empty.should == [] - end + before do + @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" + system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" + + @filename = "#{@sandbox_dir}/test_file" + @contents = "1234" + system "echo #{@contents} > #{@filename}" + @contents += "\n" + + @file = Rush::File.new(@filename) + end + + after do + system "rm -rf #{@sandbox_dir}" + end + + it "is a child of Rush::Entry" do + @file.should be_kind_of(Rush::Entry) + end + + it "is not a dir" do + @file.should_not be_dir + end + + it "can create itself as a blank file, and return itself" do + create_me = Rush::File.new("#{@sandbox_dir}/create_me") + create_me.create.should == create_me + File.exists?("#{@sandbox_dir}/create_me").should == true + end + + it "knows its size in bytes" do + @file.size.should == @contents.length + end + + it "can read its contents" do + @file.contents.should == @contents + end + + it "read is an alias for contents" do + @file.read.should == @contents + end + + it "can write new contents" do + @file.write('write test') + @file.contents.should == 'write test' + end + + it "can count the number of lines it contains" do + @file.write("1\n2\n3\n") + @file.line_count.should == 3 + end + + it "searches its contents for matching lines" do + @file.write("a\n1\nb\n2\n") + @file.search(/\d/).should == %w(1 2) + end + + it "search returns nil if no lines match" do + @file.write("a\nb\nc\n") + @file.search(/\d/).should be_nil + end + + it "find-in-file replace" do + @file.replace_contents!(/\d/, 'x') + @file.contents.should == "xxxx\n" + end + + it "can destroy itself" do + @file.destroy + ::File.exists?(@filename).should eq(false) + end + + it "can fetch contents or blank if doesn't exist" do + Rush::File.new('/does/not/exist').contents_or_blank.should == "" + end + + it "can fetch lines, or empty if doesn't exist" do + Rush::File.new('/does/not/exist').lines_or_empty.should == [] + end end diff --git a/spec/local_spec.rb b/spec/local_spec.rb index 2b47ca6..e31bfac 100644 --- a/spec/local_spec.rb +++ b/spec/local_spec.rb @@ -1,357 +1,357 @@ require File.dirname(__FILE__) + '/base' describe Rush::Connection::Local do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - - @con = Rush::Connection::Local.new - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "receive -> write_file(file, contents)" do - @con.should_receive(:write_file).with('file', 'contents') - @con.receive(:action => 'write_file', :full_path => 'file', :payload => 'contents') - end - - it "receive -> append_to_file(file, contents)" do - @con.should_receive(:append_to_file).with('file', 'contents') - @con.receive(:action => 'append_to_file', :full_path => 'file', :payload => 'contents') - end - - it "receive -> file_contents(file)" do - @con.should_receive(:file_contents).with('file').and_return('the contents') - @con.receive(:action => 'file_contents', :full_path => 'file').should == 'the contents' - end - - it "receive -> destroy(file or dir)" do - @con.should_receive(:destroy).with('file') - @con.receive(:action => 'destroy', :full_path => 'file') - end - - it "receive -> purge(dir)" do - @con.should_receive(:purge).with('dir') - @con.receive(:action => 'purge', :full_path => 'dir') - end - - it "receive -> create_dir(path)" do - @con.should_receive(:create_dir).with('dir') - @con.receive(:action => 'create_dir', :full_path => 'dir') - end - - it "receive -> rename(path, name, new_name)" do - @con.should_receive(:rename).with('path', 'name', 'new_name') - @con.receive(:action => 'rename', :path => 'path', :name => 'name', :new_name => 'new_name') - end - - it "receive -> copy(src, dst)" do - @con.should_receive(:copy).with('src', 'dst') - @con.receive(:action => 'copy', :src => 'src', :dst => 'dst') - end - - it "receive -> read_archive(full_path)" do - @con.should_receive(:read_archive).with('full_path').and_return('archive data') - @con.receive(:action => 'read_archive', :full_path => 'full_path').should == 'archive data' - end - - it "receive -> write_archive(archive, dir)" do - @con.should_receive(:write_archive).with('archive', 'dir') - @con.receive(:action => 'write_archive', :dir => 'dir', :payload => 'archive') - end - - it "receive -> index(base_path, glob)" do - @con.should_receive(:index).with('base_path', '*').and_return(%w(1 2)) - @con.receive(:action => 'index', :base_path => 'base_path', :glob => '*').should == "1\n2\n" - end - - it "receive -> stat(full_path)" do - @con.should_receive(:stat).with('full_path').and_return(1 => 2) - @con.receive(:action => 'stat', :full_path => 'full_path').should == YAML.dump(1 => 2) - end - - it "receive -> set_access(full_path, user, group, permissions)" do - access = mock("access") - Rush::Access.should_receive(:from_hash).with(:action => 'set_access', :full_path => 'full_path', :user => 'joe').and_return(access) - - @con.should_receive(:set_access).with('full_path', access) - @con.receive(:action => 'set_access', :full_path => 'full_path', :user => 'joe') - end - - it "receive -> size(full_path)" do - @con.should_receive(:size).with('full_path').and_return("1024") - @con.receive(:action => 'size', :full_path => 'full_path').should == "1024" - end - - it "receive -> processes" do - @con.should_receive(:processes).with().and_return([ { :pid => 1 } ]) - @con.receive(:action => 'processes').should == YAML.dump([ { :pid => 1 } ]) - end - - it "receive -> process_alive" do - @con.should_receive(:process_alive).with(123).and_return(true) - @con.receive(:action => 'process_alive', :pid => 123).should == '1' - end - - it "receive -> kill_process" do - @con.should_receive(:kill_process).with(123, :wait => 10).and_return(true) - @con.receive(:action => 'kill_process', :pid => '123', :payload => YAML.dump(:wait => 10)) - end - - it "receive -> bash (reset environment)" do - @con.should_receive(:bash).with('cmd', 'user', false, true).and_return('output') - @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'false', :reset_environment => 'true').should == 'output' - end - - it "receive -> bash (foreground)" do - @con.should_receive(:bash).with('cmd', 'user', false, false).and_return('output') - @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'false').should == 'output' - end - - it "receive -> bash (background)" do - @con.should_receive(:bash).with('cmd', 'user', true, false).and_return('output') - @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'true').should == 'output' - end - - it "receive -> unknown action exception" do - lambda { @con.receive(:action => 'does_not_exist') }.should raise_error(Rush::Connection::Local::UnknownAction) - end - - it "write_file writes contents to a file" do - fname = "#{@sandbox_dir}/a_file" - data = "some data" - @con.write_file(fname, data) - File.read(fname).should == data - end - - it "append_to_file appends contents to a file" do - fname = "#{@sandbox_dir}/a_file" - system "echo line1 > #{fname}" - @con.append_to_file(fname, 'line2') - File.read(fname).should == "line1\nline2" - end - - it "file_contents reads a file's contents" do - fname = "#{@sandbox_dir}/a_file" - system "echo stuff > #{fname}" - @con.file_contents(fname).should == "stuff\n" - end - - it "file_contents raises DoesNotExist if the file does not exist" do - fname = "#{@sandbox_dir}/does_not_exist" - lambda { @con.file_contents(fname) }.should raise_error(Rush::DoesNotExist, fname) - end - - it "destroy to destroy a file or dir" do - fname = "#{@sandbox_dir}/delete_me" - system "touch #{fname}" - @con.destroy(fname) - File.exists?(fname).should be_false - end - - it "purge to purge a dir" do - system "cd #{@sandbox_dir}; touch {1,2}; mkdir 3; touch 3/4" - @con.purge(@sandbox_dir) - File.exists?(@sandbox_dir).should be_true - Dir.glob("#{@sandbox_dir}/*").should == [] - end - - it "purge kills hidden (dotfile) entries too" do - system "cd #{@sandbox_dir}; touch .killme" - @con.purge(@sandbox_dir) - File.exists?(@sandbox_dir).should be_true - `cd #{@sandbox_dir}; ls -lA | grep -v total | wc -l`.to_i.should == 0 - end - - it "create_dir creates a directory" do - fname = "#{@sandbox_dir}/a/b/c/" - @con.create_dir(fname) - File.directory?(fname).should be_true - end - - it "rename to rename entries within a dir" do - system "touch #{@sandbox_dir}/a" - @con.rename(@sandbox_dir, 'a', 'b') - File.exists?("#{@sandbox_dir}/a").should be_false - File.exists?("#{@sandbox_dir}/b").should be_true - end - - it "copy to copy an entry to another dir on the same box" do - system "mkdir #{@sandbox_dir}/subdir" - system "touch #{@sandbox_dir}/a" - @con.copy("#{@sandbox_dir}/a", "#{@sandbox_dir}/subdir") - File.exists?("#{@sandbox_dir}/a").should be_true - File.exists?("#{@sandbox_dir}/subdir/a").should be_true - end - - it "copy raises DoesNotExist with source path if it doesn't exist or otherwise can't be accessed" do - lambda { @con.copy('/does/not/exist', '/tmp') }.should raise_error(Rush::DoesNotExist, '/does/not/exist') - end - - it "copy raises DoesNotExist with destination path if it can't access the destination" do - lambda { @con.copy('/tmp', '/does/not/exist') }.should raise_error(Rush::DoesNotExist, '/does/not') - end - - it "read_archive to pull an archive of a dir into a byte stream" do - system "touch #{@sandbox_dir}/a" - @con.read_archive(@sandbox_dir).size.should > 50 - end - - it "read_archive works for paths with spaces" do - system "mkdir -p #{@sandbox_dir}/with\\ space; touch #{@sandbox_dir}/with\\ space/a" - @con.read_archive("#{@sandbox_dir}/with space").size.should > 50 - end - - it "write_archive to turn a byte stream into a dir" do - system "cd #{@sandbox_dir}; mkdir -p a; touch a/b; tar cf xfer.tar a; mkdir dst" - archive = File.read("#{@sandbox_dir}/xfer.tar") - @con.write_archive(archive, "#{@sandbox_dir}/dst") - File.directory?("#{@sandbox_dir}/dst/a").should be_true - File.exists?("#{@sandbox_dir}/dst/a/b").should be_true - end - - it "write_archive works for paths with spaces" do - system "cd #{@sandbox_dir}; mkdir -p a; touch a/b; tar cf xfer.tar a; mkdir with\\ space" - archive = File.read("#{@sandbox_dir}/xfer.tar") - @con.write_archive(archive, "#{@sandbox_dir}/with space") - File.directory?("#{@sandbox_dir}/with space/a").should be_true - File.exists?("#{@sandbox_dir}/with space/a/b").should be_true - end - - it "index fetches list of all files and dirs in a dir when pattern is empty" do - system "cd #{@sandbox_dir}; mkdir dir; touch file" - @con.index(@sandbox_dir, '').should == [ 'dir/', 'file' ] - end - - it "index fetches only files with a certain extension with a flat pattern, *.rb" do - system "cd #{@sandbox_dir}; touch a.rb; touch b.txt" - @con.index(@sandbox_dir, '*.rb').should == [ 'a.rb' ] - end - - it "index raises DoesNotExist when the base path is invalid" do - lambda { @con.index('/does/not/exist', '*') }.should raise_error(Rush::DoesNotExist, '/does/not/exist') - end - - it "stat gives file stats like size and timestamps" do - @con.stat(@sandbox_dir).should have_key(:ctime) - @con.stat(@sandbox_dir).should have_key(:size) - end - - it "stat fetches the octal permissions" do - @con.stat(@sandbox_dir)[:mode].should be_kind_of(Fixnum) - end - - it "stat raises DoesNotExist if the entry does not exist" do - fname = "#{@sandbox_dir}/does_not_exist" - lambda { @con.stat(fname) }.should raise_error(Rush::DoesNotExist, fname) - end - - it "set_access invokes the access object" do - access = mock("access") - access.should_receive(:apply).with('/some/path') - @con.set_access('/some/path', access) - end - - if !RUBY_PLATFORM.match(/darwin/) # doesn't work on OS X 'cause du switches are different - it "size gives size of a directory and all its contents recursively" do - system "mkdir -p #{@sandbox_dir}/a/b/; echo 1234 > #{@sandbox_dir}/a/b/c" - @con.size(@sandbox_dir).should == (4096*3 + 5) - end - end - - it "parses ps output on os x" do - @con.parse_ps("21712 501 21711 1236 0 /usr/bin/vi somefile.rb").should == { - :pid => "21712", - :uid => "501", - :parent_pid => 21711, - :mem => 1236, - :cpu => 0, - :command => '/usr/bin/vi', - :cmdline => '/usr/bin/vi somefile.rb', - } - end - - it "gets the list of processes on os x via the ps command" do - @con.should_receive(:os_x_raw_ps).and_return < write_file(file, contents)" do + @con.should_receive(:write_file).with('file', 'contents') + @con.receive(:action => 'write_file', :full_path => 'file', :payload => 'contents') + end + + it "receive -> append_to_file(file, contents)" do + @con.should_receive(:append_to_file).with('file', 'contents') + @con.receive(:action => 'append_to_file', :full_path => 'file', :payload => 'contents') + end + + it "receive -> file_contents(file)" do + @con.should_receive(:file_contents).with('file').and_return('the contents') + @con.receive(:action => 'file_contents', :full_path => 'file').should == 'the contents' + end + + it "receive -> destroy(file or dir)" do + @con.should_receive(:destroy).with('file') + @con.receive(:action => 'destroy', :full_path => 'file') + end + + it "receive -> purge(dir)" do + @con.should_receive(:purge).with('dir') + @con.receive(:action => 'purge', :full_path => 'dir') + end + + it "receive -> create_dir(path)" do + @con.should_receive(:create_dir).with('dir') + @con.receive(:action => 'create_dir', :full_path => 'dir') + end + + it "receive -> rename(path, name, new_name)" do + @con.should_receive(:rename).with('path', 'name', 'new_name') + @con.receive(:action => 'rename', :path => 'path', :name => 'name', :new_name => 'new_name') + end + + it "receive -> copy(src, dst)" do + @con.should_receive(:copy).with('src', 'dst') + @con.receive(:action => 'copy', :src => 'src', :dst => 'dst') + end + + it "receive -> read_archive(full_path)" do + @con.should_receive(:read_archive).with('full_path').and_return('archive data') + @con.receive(:action => 'read_archive', :full_path => 'full_path').should == 'archive data' + end + + it "receive -> write_archive(archive, dir)" do + @con.should_receive(:write_archive).with('archive', 'dir') + @con.receive(:action => 'write_archive', :dir => 'dir', :payload => 'archive') + end + + it "receive -> index(base_path, glob)" do + @con.should_receive(:index).with('base_path', '*').and_return(%w(1 2)) + @con.receive(:action => 'index', :base_path => 'base_path', :glob => '*').should == "1\n2\n" + end + + it "receive -> stat(full_path)" do + @con.should_receive(:stat).with('full_path').and_return(1 => 2) + @con.receive(:action => 'stat', :full_path => 'full_path').should == YAML.dump(1 => 2) + end + + it "receive -> set_access(full_path, user, group, permissions)" do + access = double("access") + Rush::Access.should_receive(:from_hash).with(:action => 'set_access', :full_path => 'full_path', :user => 'joe').and_return(access) + + @con.should_receive(:set_access).with('full_path', access) + @con.receive(:action => 'set_access', :full_path => 'full_path', :user => 'joe') + end + + it "receive -> size(full_path)" do + @con.should_receive(:size).with('full_path').and_return("1024") + @con.receive(:action => 'size', :full_path => 'full_path').should == "1024" + end + + it "receive -> processes" do + @con.should_receive(:processes).with(no_args).and_return([ { :pid => 1 } ]) + @con.receive(:action => 'processes').should == YAML.dump([ { :pid => 1 } ]) + end + + it "receive -> process_alive" do + @con.should_receive(:process_alive).with(123).and_return(true) + @con.receive(:action => 'process_alive', :pid => 123).should == '1' + end + + it "receive -> kill_process" do + @con.should_receive(:kill_process).with(123, :wait => 10).and_return(true) + @con.receive(:action => 'kill_process', :pid => '123', :payload => YAML.dump(:wait => 10)) + end + + it "receive -> bash (reset environment)" do + @con.should_receive(:bash).with('cmd', 'user', false, true).and_return('output') + @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'false', :reset_environment => 'true').should == 'output' + end + + it "receive -> bash (foreground)" do + @con.should_receive(:bash).with('cmd', 'user', false, false).and_return('output') + @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'false').should == 'output' + end + + it "receive -> bash (background)" do + @con.should_receive(:bash).with('cmd', 'user', true, false).and_return('output') + @con.receive(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'true').should == 'output' + end + + it "receive -> unknown action exception" do + lambda { @con.receive(:action => 'does_not_exist') }.should raise_error(Rush::Connection::Local::UnknownAction) + end + + it "write_file writes contents to a file" do + fname = "#{@sandbox_dir}/a_file" + data = "some data" + @con.write_file(fname, data) + File.read(fname).should == data + end + + it "append_to_file appends contents to a file" do + fname = "#{@sandbox_dir}/a_file" + system "echo line1 > #{fname}" + @con.append_to_file(fname, 'line2') + File.read(fname).should == "line1\nline2" + end + + it "file_contents reads a file's contents" do + fname = "#{@sandbox_dir}/a_file" + system "echo stuff > #{fname}" + @con.file_contents(fname).should == "stuff\n" + end + + it "file_contents raises DoesNotExist if the file does not exist" do + fname = "#{@sandbox_dir}/does_not_exist" + lambda { @con.file_contents(fname) }.should raise_error(Rush::DoesNotExist, fname) + end + + it "destroy to destroy a file or dir" do + fname = "#{@sandbox_dir}/delete_me" + system "touch #{fname}" + @con.destroy(fname) + File.exists?(fname).should eq(false) + end + + it "purge to purge a dir" do + system "cd #{@sandbox_dir}; touch {1,2}; mkdir 3; touch 3/4" + @con.purge(@sandbox_dir) + File.exists?(@sandbox_dir).should eq(true) + Dir.glob("#{@sandbox_dir}/*").should == [] + end + + it "purge kills hidden (dotfile) entries too" do + system "cd #{@sandbox_dir}; touch .killme" + @con.purge(@sandbox_dir) + File.exists?(@sandbox_dir).should eq(true) + `cd #{@sandbox_dir}; ls -lA | grep -v total | wc -l`.to_i.should == 0 + end + + it "create_dir creates a directory" do + fname = "#{@sandbox_dir}/a/b/c/" + @con.create_dir(fname) + File.directory?(fname).should eq(true) + end + + it "rename to rename entries within a dir" do + system "touch #{@sandbox_dir}/a" + @con.rename(@sandbox_dir, 'a', 'b') + File.exists?("#{@sandbox_dir}/a").should eq(false) + File.exists?("#{@sandbox_dir}/b").should eq(true) + end + + it "copy to copy an entry to another dir on the same box" do + system "mkdir #{@sandbox_dir}/subdir" + system "touch #{@sandbox_dir}/a" + @con.copy("#{@sandbox_dir}/a", "#{@sandbox_dir}/subdir") + File.exists?("#{@sandbox_dir}/a").should eq(true) + File.exists?("#{@sandbox_dir}/subdir/a").should eq(true) + end + + it "copy raises DoesNotExist with source path if it doesn't exist or otherwise can't be accessed" do + lambda { @con.copy('/does/not/exist', '/tmp') }.should raise_error(Rush::DoesNotExist, '/does/not/exist') + end + + it "copy raises DoesNotExist with destination path if it can't access the destination" do + lambda { @con.copy('/tmp', '/does/not/exist') }.should raise_error(Rush::DoesNotExist, '/does/not') + end + + it "read_archive to pull an archive of a dir into a byte stream" do + system "touch #{@sandbox_dir}/a" + @con.read_archive(@sandbox_dir).size.should > 50 + end + + it "read_archive works for paths with spaces" do + system "mkdir -p #{@sandbox_dir}/with\\ space; touch #{@sandbox_dir}/with\\ space/a" + @con.read_archive("#{@sandbox_dir}/with space").size.should > 50 + end + + it "write_archive to turn a byte stream into a dir" do + system "cd #{@sandbox_dir}; mkdir -p a; touch a/b; tar cf xfer.tar a; mkdir dst" + archive = File.read("#{@sandbox_dir}/xfer.tar") + @con.write_archive(archive, "#{@sandbox_dir}/dst") + File.directory?("#{@sandbox_dir}/dst/a").should eq(true) + File.exists?("#{@sandbox_dir}/dst/a/b").should eq(true) + end + + it "write_archive works for paths with spaces" do + system "cd #{@sandbox_dir}; mkdir -p a; touch a/b; tar cf xfer.tar a; mkdir with\\ space" + archive = File.read("#{@sandbox_dir}/xfer.tar") + @con.write_archive(archive, "#{@sandbox_dir}/with space") + File.directory?("#{@sandbox_dir}/with space/a").should eq(true) + File.exists?("#{@sandbox_dir}/with space/a/b").should eq(true) + end + + it "index fetches list of all files and dirs in a dir when pattern is empty" do + system "cd #{@sandbox_dir}; mkdir dir; touch file" + @con.index(@sandbox_dir, '').should == [ 'dir/', 'file' ] + end + + it "index fetches only files with a certain extension with a flat pattern, *.rb" do + system "cd #{@sandbox_dir}; touch a.rb; touch b.txt" + @con.index(@sandbox_dir, '*.rb').should == [ 'a.rb' ] + end + + it "index raises DoesNotExist when the base path is invalid" do + lambda { @con.index('/does/not/exist', '*') }.should raise_error(Rush::DoesNotExist, '/does/not/exist') + end + + it "stat gives file stats like size and timestamps" do + @con.stat(@sandbox_dir).should have_key(:ctime) + @con.stat(@sandbox_dir).should have_key(:size) + end + + it "stat fetches the octal permissions" do + @con.stat(@sandbox_dir)[:mode].should be_kind_of(Fixnum) + end + + it "stat raises DoesNotExist if the entry does not exist" do + fname = "#{@sandbox_dir}/does_not_exist" + lambda { @con.stat(fname) }.should raise_error(Rush::DoesNotExist, fname) + end + + it "set_access invokes the access object" do + access = double("access") + access.should_receive(:apply).with('/some/path') + @con.set_access('/some/path', access) + end + + if !RUBY_PLATFORM.match(/darwin/) # doesn't work on OS X 'cause du switches are different + it "size gives size of a directory and all its contents recursively" do + system "mkdir -p #{@sandbox_dir}/a/b/; echo 1234 > #{@sandbox_dir}/a/b/c" + @con.size(@sandbox_dir).should == (4096*3 + 5) + end + end + + it "parses ps output on os x" do + @con.parse_ps("21712 501 21711 1236 0 /usr/bin/vi somefile.rb").should == { + :pid => "21712", + :uid => "501", + :parent_pid => 21711, + :mem => 1236, + :cpu => 0, + :command => '/usr/bin/vi', + :cmdline => '/usr/bin/vi somefile.rb', + } + end + + it "gets the list of processes on os x via the ps command" do + @con.should_receive(:os_x_raw_ps).and_return < "1", :uid => "0", :parent_pid => 1, :mem => 1111, :cpu => 0, :command => "cmd1", :cmdline => "cmd1 args" }, - { :pid => "2", :uid => "501", :parent_pid => 1, :mem => 222, :cpu => 1, :command => "cmd2", :cmdline => "cmd2" }, - ] - end - - it "the current process should be alive" do - @con.process_alive(Process.pid).should be_true - end - - it "a made-up process should not be alive" do - @con.process_alive(99999).should be_false - end - - it "kills a process by pid sending a TERM" do - @con.stub!(:process_alive).and_return(false) - ::Process.should_receive(:kill).with('TERM', 123).once - @con.kill_process(123) - end - - it "kills a process by pid sending a KILL signal if TERM doesn't work" do - @con.stub!(:process_alive).and_return(true) - ::Process.should_receive(:kill).with('TERM', 123).at_least(:twice) - ::Process.should_receive(:kill).with('KILL', 123) - @con.kill_process(123) - end - - it "kills a process by pid without sending TERM if :wait is zero" do - ::Process.should_not_receive(:kill).with('TERM', 123) - ::Process.should_receive(:kill).with('KILL', 123) - @con.kill_process(123, :wait => 0) - end - - it "does not raise an error if the process is already dead" do - ::Process.should_receive(:kill).and_raise(Errno::ESRCH) - lambda { @con.kill_process(123) }.should_not raise_error - end - - it "executes a bash command, returning stdout when successful" do - @con.bash("echo test").should == "test\n" - end - - it "executes a bash command, raising and error (with stderr as the message) when return value is nonzero" do - lambda { @con.bash("no_such_bin") }.should raise_error(Rush::BashFailed, /command not found/) - end - - it "executes a bash command as another user using sudo" do - @con.bash("echo test2", ENV['USER']).should == "test2\n" - end - - it "executes a bash command in the background, returning the pid" do - @con.bash("true", nil, true).should > 0 - end - - it "ensure_tunnel to match with remote connection" do - @con.ensure_tunnel - end - - it "always returns true on alive?" do - @con.should be_alive - end - - it "resolves a unix uid to a user" do - @con.resolve_unix_uid_to_user(0).should == "root" - @con.resolve_unix_uid_to_user('0').should == "root" - end - - it "returns nil if the unix uid does not exist" do - @con.resolve_unix_uid_to_user(9999).should be_nil - end - - it "iterates through a process list and resolves the unix uid for each" do - list = [ { :uid => 0, :command => 'pureftpd' }, { :uid => 9999, :command => 'defunk' } ] - @con.resolve_unix_uids(list).should == [ { :uid => 0, :user => 'root', :command => 'pureftpd' }, { :uid => 9999, :command => 'defunk', :user => nil } ] - end + @con.os_x_processes.should == [ + { :pid => "1", :uid => "0", :parent_pid => 1, :mem => 1111, :cpu => 0, :command => "cmd1", :cmdline => "cmd1 args" }, + { :pid => "2", :uid => "501", :parent_pid => 1, :mem => 222, :cpu => 1, :command => "cmd2", :cmdline => "cmd2" }, + ] + end + + it "the current process should be alive" do + @con.process_alive(Process.pid).should eq(true) + end + + it "a made-up process should not be alive" do + @con.process_alive(99999).should eq(false) + end + + it "kills a process by pid sending a TERM" do + allow(@con).to receive(:process_alive).and_return(false) + expect(::Process).to receive(:kill).with('TERM', 123).once + @con.kill_process(123) + end + + it "kills a process by pid sending a KILL signal if TERM doesn't work" do + allow(@con).to receive(:process_alive).and_return(true) + ::Process.should_receive(:kill).with('TERM', 123).at_least(:twice) + ::Process.should_receive(:kill).with('KILL', 123) + @con.kill_process(123) + end + + it "kills a process by pid without sending TERM if :wait is zero" do + ::Process.should_not_receive(:kill).with('TERM', 123) + ::Process.should_receive(:kill).with('KILL', 123) + @con.kill_process(123, :wait => 0) + end + + it "does not raise an error if the process is already dead" do + ::Process.should_receive(:kill).and_raise(Errno::ESRCH) + lambda { @con.kill_process(123) }.should_not raise_error + end + + it "executes a bash command, returning stdout when successful" do + @con.bash("echo test").should == "test\n" + end + + it "executes a bash command, raising and error (with stderr as the message) when return value is nonzero" do + lambda { @con.bash("no_such_bin") }.should raise_error(Rush::BashFailed, /command not found/) + end + + it "executes a bash command as another user using sudo" do + @con.bash("echo test2", ENV['USER']).should == "test2\n" + end + + it "executes a bash command in the background, returning the pid" do + @con.bash("true", nil, true).should > 0 + end + + it "ensure_tunnel to match with remote connection" do + @con.ensure_tunnel + end + + it "always returns true on alive?" do + @con.should be_alive + end + + it "resolves a unix uid to a user" do + @con.resolve_unix_uid_to_user(0).should == "root" + @con.resolve_unix_uid_to_user('0').should == "root" + end + + it "returns nil if the unix uid does not exist" do + @con.resolve_unix_uid_to_user(9999).should be_nil + end + + it "iterates through a process list and resolves the unix uid for each" do + list = [ { :uid => 0, :command => 'pureftpd' }, { :uid => 9999, :command => 'defunk' } ] + @con.resolve_unix_uids(list).should == [ { :uid => 0, :user => 'root', :command => 'pureftpd' }, { :uid => 9999, :command => 'defunk', :user => nil } ] + end end diff --git a/spec/process_set_spec.rb b/spec/process_set_spec.rb index 56a3b02..2be5de5 100644 --- a/spec/process_set_spec.rb +++ b/spec/process_set_spec.rb @@ -1,50 +1,50 @@ require File.dirname(__FILE__) + '/base' describe Rush::ProcessSet do - before do - @process = mock('process') - @set = Rush::ProcessSet.new([ @process ]) - end - - it "is Enumerable" do - @set.select { |s| s == @process }.should == [ @process ] - end - - it "defines size" do - @set.size.should == 1 - end - - it "defines first" do - @set.first.should == @process - end - - it "is equal to sets with the same contents" do - @set.should == Rush::ProcessSet.new([ @process ]) - end - - it "is equal to arrays with the same contents" do - @set.should == [ @process ] - end - - it "kills all processes in the set" do - @process.should_receive(:kill) - @set.kill - end - - it "checks the alive? state of all processes in the set" do - @process.should_receive(:alive?).and_return(true) - @set.alive?.should == [ true ] - end - - it "filters the set from a conditions hash and returns the filtered set" do - @process.stub!(:pid).and_return(123) - @set.filter(:pid => 123).first.should == @process - @set.filter(:pid => 456).size.should == 0 - end - - it "filters with regexps if provided in the conditions" do - @process.stub!(:command).and_return('foobaz') - @set.filter(:command => /baz/).first.should == @process - @set.filter(:command => /blerg/).size.should == 0 - end + before do + @process = double('process') + @set = Rush::ProcessSet.new([ @process ]) + end + + it "is Enumerable" do + @set.select { |s| s == @process }.should == [ @process ] + end + + it "defines size" do + @set.size.should == 1 + end + + it "defines first" do + @set.first.should == @process + end + + it "is equal to sets with the same contents" do + @set.should == Rush::ProcessSet.new([ @process ]) + end + + it "is equal to arrays with the same contents" do + @set.should == [ @process ] + end + + it "kills all processes in the set" do + @process.should_receive(:kill) + @set.kill + end + + it "checks the alive? state of all processes in the set" do + @process.should_receive(:alive?).and_return(true) + @set.alive?.should == [ true ] + end + + it "filters the set from a conditions hash and returns the filtered set" do + allow(@process).to receive(:pid).and_return(123) + @set.filter(:pid => 123).first.should == @process + @set.filter(:pid => 456).size.should == 0 + end + + it "filters with regexps if provided in the conditions" do + allow(@process).to receive(:command).and_return('foobaz') + @set.filter(:command => /baz/).first.should == @process + @set.filter(:command => /blerg/).size.should == 0 + end end diff --git a/spec/process_spec.rb b/spec/process_spec.rb index 7f09feb..36a9a45 100644 --- a/spec/process_spec.rb +++ b/spec/process_spec.rb @@ -1,73 +1,73 @@ require File.dirname(__FILE__) + '/base' describe Rush::Process do - before do - @pid = fork do - sleep 999 - end - @process = Rush::Process.all.detect { |p| p.pid == @pid } - end + before do + @pid = fork do + sleep 999 + end + @process = Rush::Process.all.detect { |p| p.pid == @pid } + end - after do - system "kill -9 #{@pid}" - end + after do + system "kill -9 #{@pid}" + end - if !RUBY_PLATFORM.match(/darwin/) # OS x reports pids weird - it "knows all its child processes" do - parent = Rush::Process.all.detect { |p| p.pid == Process.pid } - parent.children.should == [ @process ] - end - end + if !RUBY_PLATFORM.match(/darwin/) # OS x reports pids weird + it "knows all its child processes" do + parent = Rush::Process.all.detect { |p| p.pid == Process.pid } + parent.children.should == [ @process ] + end + end - it "gets the list of all processes" do - list = Rush::Process.all - list.size.should > 5 - list.first.should be_kind_of(Rush::Process) - end + it "gets the list of all processes" do + list = Rush::Process.all + list.size.should > 5 + list.first.should be_kind_of(Rush::Process) + end - it "knows the pid" do - @process.pid.should == @pid - end + it "knows the pid" do + @process.pid.should == @pid + end - it "knows the uid" do - @process.uid.should == ::Process.uid - end + it "knows the uid" do + @process.uid.should == ::Process.uid + end - it "knows the executed binary" do - @process.command.should match(/ruby/) - end + it "knows the executed binary" do + @process.command.should match(/ruby/) + end - it "knows the command line" do - @process.cmdline.should match(/process_spec.rb/) - end + it "knows the command line" do + @process.cmdline.should match($PROGRAM_NAME) + end - it "knows the memory used" do - @process.mem.should > 0 - end + it "knows the memory used" do + @process.mem.should > 0 + end - it "knows the cpu used" do - @process.cpu.should >= 0 - end + it "knows the cpu used" do + @process.cpu.should >= 0 + end - it "knows the parent process pid" do - @process.parent_pid.should == Process.pid - end + it "knows the parent process pid" do + @process.parent_pid.should == Process.pid + end - it "knows the parent process" do - this = Rush::Box.new.processes.select { |p| p.pid == Process.pid }.first - @process.parent.should == this - end + it "knows the parent process" do + this = Rush::Box.new.processes.select { |p| p.pid == Process.pid }.first + @process.parent.should == this + end - it "can kill itself" do - process = Rush.bash("sleep 30", :background => true) - process.alive?.should be_true - process.kill - sleep 0.1 - process.alive?.should be_false - end + it "can kill itself" do + process = Rush.bash("sleep 30", :background => true) + process.alive?.should eq(true) + process.kill + sleep 0.1 + process.alive?.should eq(false) + end - it "if box and pid are the same, process is equal" do - other = Rush::Process.new({ :pid => @process.pid }, @process.box) - @process.should == other - end + it "if box and pid are the same, process is equal" do + other = Rush::Process.new({ :pid => @process.pid }, @process.box) + @process.should == other + end end diff --git a/spec/remote_spec.rb b/spec/remote_spec.rb index d7a6cc1..e3f30dd 100644 --- a/spec/remote_spec.rb +++ b/spec/remote_spec.rb @@ -1,140 +1,140 @@ require File.dirname(__FILE__) + '/base' describe Rush::Connection::Local do - before do - @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" - system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" - - @con = Rush::Connection::Remote.new('spec.example.com') - end - - after do - system "rm -rf #{@sandbox_dir}" - end - - it "transmits write_file" do - @con.should_receive(:transmit).with(:action => 'write_file', :full_path => 'file', :payload => 'contents') - @con.write_file('file', 'contents') - end - - it "transmits append_to_file" do - @con.should_receive(:transmit).with(:action => 'append_to_file', :full_path => 'file', :payload => 'contents') - @con.append_to_file('file', 'contents') - end - - it "transmits file_contents" do - @con.should_receive(:transmit).with(:action => 'file_contents', :full_path => 'file').and_return('contents') - @con.file_contents('file').should == 'contents' - end - - it "transmits destroy" do - @con.should_receive(:transmit).with(:action => 'destroy', :full_path => 'file') - @con.destroy('file') - end - - it "transmits purge" do - @con.should_receive(:transmit).with(:action => 'purge', :full_path => 'dir') - @con.purge('dir') - end - - it "transmits create_dir" do - @con.should_receive(:transmit).with(:action => 'create_dir', :full_path => 'file') - @con.create_dir('file') - end - - it "transmits rename" do - @con.should_receive(:transmit).with(:action => 'rename', :path => 'path', :name => 'name', :new_name => 'new_name') - @con.rename('path', 'name', 'new_name') - end - - it "transmits copy" do - @con.should_receive(:transmit).with(:action => 'copy', :src => 'src', :dst => 'dst') - @con.copy('src', 'dst') - end - - it "transmits read_archive" do - @con.should_receive(:transmit).with(:action => 'read_archive', :full_path => 'full_path').and_return('archive data') - @con.read_archive('full_path').should == 'archive data' - end - - it "transmits write_archive" do - @con.should_receive(:transmit).with(:action => 'write_archive', :dir => 'dir', :payload => 'archive') - @con.write_archive('archive', 'dir') - end - - it "transmits index" do - @con.should_receive(:transmit).with(:action => 'index', :base_path => 'base_path', :glob => '*').and_return("1\n2\n") - @con.index('base_path', '*').should == %w(1 2) - end - - it "transmits stat" do - @con.should_receive(:transmit).with(:action => 'stat', :full_path => 'full_path').and_return(YAML.dump(1 => 2)) - @con.stat('full_path').should == { 1 => 2 } - end - - it "transmits set_access" do - @con.should_receive(:transmit).with(:action => 'set_access', :full_path => 'full_path', :user => 'joe', :user_read => 1) - @con.set_access('full_path', :user => 'joe', :user_read => 1) - end - - it "transmits size" do - @con.should_receive(:transmit).with(:action => 'size', :full_path => 'full_path').and_return("123") - @con.size('full_path').should == 123 - end - - it "transmits processes" do - @con.should_receive(:transmit).with(:action => 'processes').and_return(YAML.dump([ { :pid => 1 } ])) - @con.processes.should == [ { :pid => 1 } ] - end - - it "transmits process_alive" do - @con.should_receive(:transmit).with(:action => 'process_alive', :pid => 123).and_return(true) - @con.process_alive(123).should == true - end - - it "transmits kill_process" do - @con.should_receive(:transmit).with(:action => 'kill_process', :pid => 123, :payload => YAML.dump(:wait => 10)) - @con.kill_process(123, :wait => 10) - end - - it "transmits bash" do - @con.should_receive(:transmit).with(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'bg', :reset_environment => false).and_return('output') - @con.bash('cmd', 'user', 'bg', false).should == 'output' - end - - it "an http result code of 401 raises NotAuthorized" do - lambda { @con.process_result("401", "") }.should raise_error(Rush::NotAuthorized) - end - - it "an http result code of 400 raises the exception passed in the result body" do - @con.stub!(:parse_exception).and_return(Rush::DoesNotExist, "message") - lambda { @con.process_result("400", "") }.should raise_error(Rush::DoesNotExist) - end - - it "an http result code of 501 (or anything other than the other defined codes) raises FailedTransmit" do - lambda { @con.process_result("501", "") }.should raise_error(Rush::FailedTransmit) - end - - it "parse_exception takes the class from the first line and the message from the second" do - @con.parse_exception("Rush::DoesNotExist\nthe message\n").should == [ Rush::DoesNotExist, "the message" ] - end - - it "parse_exception rejects unrecognized exceptions" do - lambda { @con.parse_exception("NotARushException\n") }.should raise_error - end - - it "passes through ensure_tunnel" do - @con.tunnel.should_receive(:ensure_tunnel) - @con.ensure_tunnel - end - - it "is alive if the box is responding to commands" do - @con.should_receive(:index).and_return(:dummy) - @con.should be_alive - end - - it "not alive if an attempted command throws an exception" do - @con.should_receive(:index).and_raise(RuntimeError) - @con.should_not be_alive - end + before do + @sandbox_dir = "/tmp/rush_spec.#{Process.pid}" + system "rm -rf #{@sandbox_dir}; mkdir -p #{@sandbox_dir}" + + @con = Rush::Connection::Remote.new('spec.example.com') + end + + after do + system "rm -rf #{@sandbox_dir}" + end + + it "transmits write_file" do + @con.should_receive(:transmit).with(:action => 'write_file', :full_path => 'file', :payload => 'contents') + @con.write_file('file', 'contents') + end + + it "transmits append_to_file" do + @con.should_receive(:transmit).with(:action => 'append_to_file', :full_path => 'file', :payload => 'contents') + @con.append_to_file('file', 'contents') + end + + it "transmits file_contents" do + @con.should_receive(:transmit).with(:action => 'file_contents', :full_path => 'file').and_return('contents') + @con.file_contents('file').should == 'contents' + end + + it "transmits destroy" do + @con.should_receive(:transmit).with(:action => 'destroy', :full_path => 'file') + @con.destroy('file') + end + + it "transmits purge" do + @con.should_receive(:transmit).with(:action => 'purge', :full_path => 'dir') + @con.purge('dir') + end + + it "transmits create_dir" do + @con.should_receive(:transmit).with(:action => 'create_dir', :full_path => 'file') + @con.create_dir('file') + end + + it "transmits rename" do + @con.should_receive(:transmit).with(:action => 'rename', :path => 'path', :name => 'name', :new_name => 'new_name') + @con.rename('path', 'name', 'new_name') + end + + it "transmits copy" do + @con.should_receive(:transmit).with(:action => 'copy', :src => 'src', :dst => 'dst') + @con.copy('src', 'dst') + end + + it "transmits read_archive" do + @con.should_receive(:transmit).with(:action => 'read_archive', :full_path => 'full_path').and_return('archive data') + @con.read_archive('full_path').should == 'archive data' + end + + it "transmits write_archive" do + @con.should_receive(:transmit).with(:action => 'write_archive', :dir => 'dir', :payload => 'archive') + @con.write_archive('archive', 'dir') + end + + it "transmits index" do + @con.should_receive(:transmit).with(:action => 'index', :base_path => 'base_path', :glob => '*').and_return("1\n2\n") + @con.index('base_path', '*').should == %w(1 2) + end + + it "transmits stat" do + @con.should_receive(:transmit).with(:action => 'stat', :full_path => 'full_path').and_return(YAML.dump(1 => 2)) + @con.stat('full_path').should == { 1 => 2 } + end + + it "transmits set_access" do + @con.should_receive(:transmit).with(:action => 'set_access', :full_path => 'full_path', :user => 'joe', :user_read => 1) + @con.set_access('full_path', :user => 'joe', :user_read => 1) + end + + it "transmits size" do + @con.should_receive(:transmit).with(:action => 'size', :full_path => 'full_path').and_return("123") + @con.size('full_path').should == 123 + end + + it "transmits processes" do + @con.should_receive(:transmit).with(:action => 'processes').and_return(YAML.dump([ { :pid => 1 } ])) + @con.processes.should == [ { :pid => 1 } ] + end + + it "transmits process_alive" do + @con.should_receive(:transmit).with(:action => 'process_alive', :pid => 123).and_return(true) + @con.process_alive(123).should == true + end + + it "transmits kill_process" do + @con.should_receive(:transmit).with(:action => 'kill_process', :pid => 123, :payload => YAML.dump(:wait => 10)) + @con.kill_process(123, :wait => 10) + end + + it "transmits bash" do + @con.should_receive(:transmit).with(:action => 'bash', :payload => 'cmd', :user => 'user', :background => 'bg', :reset_environment => false).and_return('output') + @con.bash('cmd', 'user', 'bg', false).should == 'output' + end + + it "an http result code of 401 raises NotAuthorized" do + lambda { @con.process_result("401", "") }.should raise_error(Rush::NotAuthorized) + end + + it "an http result code of 400 raises the exception passed in the result body" do + allow(@con).to receive(:parse_exception).and_return(Rush::DoesNotExist, "message") + lambda { @con.process_result("400", "") }.should raise_error(Rush::DoesNotExist) + end + + it "an http result code of 501 (or anything other than the other defined codes) raises FailedTransmit" do + lambda { @con.process_result("501", "") }.should raise_error(Rush::FailedTransmit) + end + + it "parse_exception takes the class from the first line and the message from the second" do + @con.parse_exception("Rush::DoesNotExist\nthe message\n").should == [ Rush::DoesNotExist, "the message" ] + end + + it "parse_exception rejects unrecognized exceptions" do + lambda { @con.parse_exception("NotARushException\n") }.should raise_error + end + + it "passes through ensure_tunnel" do + @con.tunnel.should_receive(:ensure_tunnel) + @con.ensure_tunnel + end + + it "is alive if the box is responding to commands" do + @con.should_receive(:index).and_return(:dummy) + @con.should be_alive + end + + it "not alive if an attempted command throws an exception" do + @con.should_receive(:index).and_raise(RuntimeError) + @con.should_not be_alive + end end diff --git a/spec/rush_spec.rb b/spec/rush_spec.rb index 0d219ab..a47e0ac 100644 --- a/spec/rush_spec.rb +++ b/spec/rush_spec.rb @@ -1,28 +1,28 @@ require File.dirname(__FILE__) + '/base' describe Rush do - it "fetches a local file path" do - Rush['/etc/hosts'].full_path.should == '/etc/hosts' - end + it "fetches a local file path" do + Rush['/etc/hosts'].full_path.should == '/etc/hosts' + end - it "fetches the dir of __FILE__" do - Rush.dir(__FILE__).name.should == 'spec' - end + it "fetches the dir of __FILE__" do + Rush.dir(__FILE__).name.should == 'spec' + end - it "fetches the launch dir (aka current working directory or pwd)" do - Dir.stub!(:pwd).and_return('/tmp') - Rush.launch_dir.should == Rush::Box.new['/tmp/'] - end + it "fetches the launch dir (aka current working directory or pwd)" do + allow(Dir).to receive(:pwd).and_return('/tmp') + Rush.launch_dir.should == Rush::Box.new['/tmp/'] + end - it "runs a bash command" do - Rush.bash('echo hi').should == "hi\n" - end + it "runs a bash command" do + Rush.bash('echo hi').should == "hi\n" + end - it "gets the list of local processes" do - Rush.processes.should be_kind_of(Rush::ProcessSet) - end + it "gets the list of local processes" do + Rush.processes.should be_kind_of(Rush::ProcessSet) + end - it "gets my process" do - Rush.my_process.pid.should == Process.pid - end + it "gets my process" do + Rush.my_process.pid.should == Process.pid + end end diff --git a/spec/search_results_spec.rb b/spec/search_results_spec.rb index a57a0fb..c62ddb5 100644 --- a/spec/search_results_spec.rb +++ b/spec/search_results_spec.rb @@ -1,44 +1,44 @@ require File.dirname(__FILE__) + '/base' describe Rush::SearchResults do - before do - @results = Rush::SearchResults.new(/pat/) - @file = Rush::File.new("file") - end - - it "returns its list of entries" do - @results.add(@file, %w(a)) - @results.entries.should == [ @file ] - end - - it "only returns each entry once no matter how many line matches it has" do - @results.add(@file, %w(a b)) - @results.entries.should == [ @file ] - end - - it "returns its list of matched lines" do - @results.add(@file, %w(a b)) - @results.lines.should == %w(a b) - end - - it "returns all lines for each entry in a flattened form" do - file2 = Rush::File.new("another file") - @results.add(@file, %w(a b)) - @results.add(file2, %w(c d)) - @results.lines.should == %w(a b c d) - end - - it "returns a hash of entries_with_lines" do - @results.add(@file, %w(a)) - @results.entries_with_lines.should == { @file => %w(a) } - end - - it "mixes in Commands to operate like a dir or entry array" do - @results.methods.include?("search").should be_true - end - - it "mixes in Enumerable to behave like an array" do - @results.add(@file, %w(a)) - @results.map { |e| e }.should == [ @file ] - end + before do + @results = Rush::SearchResults.new(/pat/) + @file = Rush::File.new("file") + end + + it "returns its list of entries" do + @results.add(@file, %w(a)) + @results.entries.should == [ @file ] + end + + it "only returns each entry once no matter how many line matches it has" do + @results.add(@file, %w(a b)) + @results.entries.should == [ @file ] + end + + it "returns its list of matched lines" do + @results.add(@file, %w(a b)) + @results.lines.should == %w(a b) + end + + it "returns all lines for each entry in a flattened form" do + file2 = Rush::File.new("another file") + @results.add(@file, %w(a b)) + @results.add(file2, %w(c d)) + @results.lines.should == %w(a b c d) + end + + it "returns a hash of entries_with_lines" do + @results.add(@file, %w(a)) + @results.entries_with_lines.should == { @file => %w(a) } + end + + it "mixes in Commands to operate like a dir or entry array" do + @results.methods.include?(:"search").should eq(true) + end + + it "mixes in Enumerable to behave like an array" do + @results.add(@file, %w(a)) + @results.map { |e| e }.should == [ @file ] + end end diff --git a/spec/ssh_tunnel_spec.rb b/spec/ssh_tunnel_spec.rb index ec54252..0947634 100644 --- a/spec/ssh_tunnel_spec.rb +++ b/spec/ssh_tunnel_spec.rb @@ -1,122 +1,122 @@ require File.dirname(__FILE__) + '/base' describe Rush::SshTunnel do - before do - @tunnel = Rush::SshTunnel.new('spec.example.com') - @tunnel.stub!(:config).and_return(mock_config_start) - @tunnel.stub!(:display) - end - - after do - mock_config_cleanup - end - - it "ensure_tunnel sets everything up for the tunnel when one does not already exist" do - @tunnel.should_receive(:push_credentials) - @tunnel.should_receive(:launch_rushd) - @tunnel.should_receive(:establish_tunnel) - @tunnel.ensure_tunnel - end - - it "ensure_tunnel uses the existing port as long as the tunnel is still alive" do - @tunnel.should_receive(:tunnel_alive?).and_return(true) - @tunnel.instance_eval("@port = 2345") - @tunnel.ensure_tunnel - @tunnel.port.should == 2345 - end - - it "existing tunnel is used when it is specified in the tunnels file" do - @tunnel.config.tunnels_file.write "spec.example.com:4567\n" - @tunnel.should_receive(:tunnel_alive?).and_return(true) - @tunnel.should_not_receive(:setup_everything) - @tunnel.ensure_tunnel - @tunnel.port.should == 4567 - end - - it "tunnel host is always local" do - @tunnel.host.should == 'localhost' - end - - it "picks the first port number when there are no tunnels yet" do - @tunnel.next_available_port.should == 7771 - end - - it "picks the next port number when there is already a tunnel" do - @tunnel.config.tunnels_file.write("#{@tunnel.host}:7771") - @tunnel.next_available_port.should == 7772 - end - - it "establishes a tunnel and saves it to ~/.rush/tunnels" do - @tunnel.should_receive(:make_ssh_tunnel) - @tunnel.should_receive(:port).exactly(0).times.and_return('7771') # avoid infinite loop - @tunnel.establish_tunnel - @tunnel.config.tunnels_file.contents.should == "spec.example.com:7771\n" - end - - it "converts instance vars to options hash for ssh_tunnel_command" do - @tunnel.instance_eval("@port = 1234") - @tunnel.tunnel_options.should == { - :local_port => 1234, - :remote_port => 7770, - :ssh_host => 'spec.example.com' - } - end - - it "ssh_stall_command uses an infinite loop for :timeout => :infinite" do - @tunnel.ssh_stall_command(:timeout => :infinite).should match(/while .* sleep .* done/) - end - - it "ssh_stall_command sleeps for the number of seconds given as the :timeout option" do - @tunnel.ssh_stall_command(:timeout => 123).should == "sleep 123" - end - - it "ssh_stall_command uses the default timeout when no options are given" do - @tunnel.ssh_stall_command.should == "sleep 9000" - end - - it "constructs the ssh tunnel command (everything but stall) from the options hash" do - @tunnel.should_receive(:tunnel_options).at_least(:once).and_return( - :local_port => 123, - :remote_port => 456, - :ssh_host => 'example.com' - ) - @tunnel.ssh_tunnel_command_without_stall.should == "ssh -f -L 123:127.0.0.1:456 example.com" - end - - it "combines the tunnel command without stall and the stall command into the final command" do - @tunnel.should_receive(:ssh_tunnel_command_without_stall).and_return('ssh command') - @tunnel.should_receive(:ssh_stall_command).and_return('sleep 123') - @tunnel.ssh_tunnel_command.should == 'ssh command "sleep 123"' - end - - it "ssh_tunnel_command request that the port be set" do - @tunnel.should_receive(:tunnel_options).at_least(:once).and_return(:local_port => nil) - lambda { @tunnel.ssh_tunnel_command }.should raise_error(Rush::SshTunnel::NoPortSelectedYet) - end - - - it "push_credentials uses ssh to append to remote host's passwords file" do - @tunnel.should_receive(:ssh_append_to_credentials).and_return(true) - @tunnel.push_credentials - end - - it "launches rushd on the remote host via ssh" do - @tunnel.should_receive(:ssh) do |cmd| - cmd.should match(/rushd/) - end - @tunnel.launch_rushd - end - - it "tunnel_alive? checks whether a tunnel is still up" do - @tunnel.should_receive(:tunnel_count_command).and_return("echo 1") - @tunnel.tunnel_alive?.should be_true - end - - it "tunnel_count_command greps ps to find the ssh tunnel" do - @tunnel.should_receive(:ssh_tunnel_command_without_stall).and_return('ssh command') - command = @tunnel.tunnel_count_command - command.should match(/ps/) - command.should match(/grep/) - command.should match(/ssh command/) - end + before do + @tunnel = Rush::SshTunnel.new('spec.example.com') + allow(@tunnel).to receive(:config).and_return(mock_config_start) + allow(@tunnel).to receive(:display) + end + + after do + mock_config_cleanup + end + + it "ensure_tunnel sets everything up for the tunnel when one does not already exist" do + expect(@tunnel).to receive(:push_credentials) + expect(@tunnel).to receive(:launch_rushd) + expect(@tunnel).to receive(:establish_tunnel) + @tunnel.ensure_tunnel + end + + it "ensure_tunnel uses the existing port as long as the tunnel is still alive" do + expect(@tunnel).to receive(:tunnel_alive?).and_return(true) + @tunnel.instance_eval("@port = 2345") + @tunnel.ensure_tunnel + expect(@tunnel.port).to eq(2345) + end + + it "existing tunnel is used when it is specified in the tunnels file" do + @tunnel.config.tunnels_file.write "spec.example.com:4567\n" + @tunnel.should_receive(:tunnel_alive?).and_return(true) + @tunnel.should_not_receive(:setup_everything) + @tunnel.ensure_tunnel + @tunnel.port.should == 4567 + end + + it "tunnel host is always local" do + @tunnel.host.should == 'localhost' + end + + it "picks the first port number when there are no tunnels yet" do + @tunnel.next_available_port.should == 7771 + end + + it "picks the next port number when there is already a tunnel" do + @tunnel.config.tunnels_file.write("#{@tunnel.host}:7771") + @tunnel.next_available_port.should == 7772 + end + + it "establishes a tunnel and saves it to ~/.rush/tunnels" do + @tunnel.should_receive(:make_ssh_tunnel) + # @tunnel.should_receive(:port).exactly(0).times.and_return('7771') # avoid infinite loop + @tunnel.establish_tunnel + @tunnel.config.tunnels_file.contents.should == "spec.example.com:7771\n" + end + + it "converts instance vars to options hash for ssh_tunnel_command" do + @tunnel.instance_eval("@port = 1234") + @tunnel.tunnel_options.should == { + :local_port => 1234, + :remote_port => 7770, + :ssh_host => 'spec.example.com' + } + end + + it "ssh_stall_command uses an infinite loop for :timeout => :infinite" do + @tunnel.ssh_stall_command(:timeout => :infinite).should match(/while .* sleep .* done/) + end + + it "ssh_stall_command sleeps for the number of seconds given as the :timeout option" do + @tunnel.ssh_stall_command(:timeout => 123).should == "sleep 123" + end + + it "ssh_stall_command uses the default timeout when no options are given" do + @tunnel.ssh_stall_command.should == "sleep 9000" + end + + it "constructs the ssh tunnel command (everything but stall) from the options hash" do + @tunnel.should_receive(:tunnel_options).at_least(:once).and_return( + :local_port => 123, + :remote_port => 456, + :ssh_host => 'example.com' + ) + @tunnel.ssh_tunnel_command_without_stall.should == "ssh -f -L 123:127.0.0.1:456 example.com" + end + + it "combines the tunnel command without stall and the stall command into the final command" do + @tunnel.should_receive(:ssh_tunnel_command_without_stall).and_return('ssh command') + @tunnel.should_receive(:ssh_stall_command).and_return('sleep 123') + @tunnel.ssh_tunnel_command.should == 'ssh command "sleep 123"' + end + + it "ssh_tunnel_command request that the port be set" do + @tunnel.should_receive(:tunnel_options).at_least(:once).and_return(:local_port => nil) + lambda { @tunnel.ssh_tunnel_command }.should raise_error(Rush::SshTunnel::NoPortSelectedYet) + end + + + it "push_credentials uses ssh to append to remote host's passwords file" do + @tunnel.should_receive(:ssh_append_to_credentials).and_return(true) + @tunnel.push_credentials + end + + it "launches rushd on the remote host via ssh" do + @tunnel.should_receive(:ssh) do |cmd| + cmd.should match(/rushd/) + end + @tunnel.launch_rushd + end + + it "tunnel_alive? checks whether a tunnel is still up" do + @tunnel.should_receive(:tunnel_count_command).and_return("echo 1") + expect(@tunnel.tunnel_alive?).to eq(true) + end + + it "tunnel_count_command greps ps to find the ssh tunnel" do + @tunnel.should_receive(:ssh_tunnel_command_without_stall).and_return('ssh command') + command = @tunnel.tunnel_count_command + command.should match(/ps/) + command.should match(/grep/) + command.should match(/ssh command/) + end end