execute_shell 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ Platform (0.4.0)
5
+ highline (1.6.1)
6
+ open4 (1.0.1)
7
+ rake (0.8.7)
8
+ win32-open3-19 (0.0.1)
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ Platform (~> 0.4.0)
15
+ highline (~> 1.6.1)
16
+ open4 (~> 1.0.1)
17
+ rake (= 0.8.7)
18
+ win32-open3-19 (~> 0.0.1)
data/README ADDED
File without changes
@@ -0,0 +1,26 @@
1
+ gem_spec = Gem::Specification.new do |s|
2
+ s.name = 'execute_shell'
3
+ s.version = '0.0.1'
4
+
5
+ s.summary = 'Executes shell commands.'
6
+
7
+ s.author = 'Travis Herrick'
8
+ s.email = 'tthetoad@gmail.com'
9
+
10
+ s.license = 'MIT'
11
+
12
+ s.extra_rdoc_files << 'README'
13
+
14
+ s.require_paths = ['lib']
15
+ s.files = Dir['lib/**/*.rb', '*']
16
+ s.test_files = Dir['test/**/*.rb']
17
+
18
+ s.add_dependency 'Platform', '~> 0.4.0'
19
+ s.add_dependency 'open4', '~> 1.0.1'
20
+ s.add_dependency 'win32-open3-19', '~> 0.0.1'
21
+
22
+ s.add_development_dependency 'rake', '0.8.7'
23
+ s.add_development_dependency 'highline', '~> 1.6.1'
24
+
25
+ s.has_rdoc = true
26
+ end
data/gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'Platform', '~> 0.4.0', :group => [:rake, :test],
4
+ :require => 'platform'
5
+ gem 'open4', '~> 1.0.1', :group => [:rake, :test]
6
+ gem 'win32-open3-19', '~> 0.0.1', :group => [:rake, :test], :require => 'open3'
7
+
8
+ group :rake do
9
+ gem 'rake', '0.8.7'
10
+ gem 'highline', '~> 1.6.1', :require => 'highline/import'
11
+ end
@@ -0,0 +1,6 @@
1
+ require 'platform'
2
+ require 'open4' if [:linux].include?(Platform::IMPL)
3
+ require 'open3' if [:mingw].include?(Platform::IMPL)
4
+
5
+ require_relative 'execute_shell/script_env'
6
+ require_relative 'execute_shell/execute_shell'
@@ -0,0 +1,180 @@
1
+ # This file contains a module for returning output from console commands.
2
+
3
+ #--
4
+ ################################################################################
5
+ # Copyright (C) 2011 Travis Herrick #
6
+ ################################################################################
7
+ # #
8
+ # \v^V,^!v\^/ #
9
+ # ~% %~ #
10
+ # { _ _ } #
11
+ # ( * - ) #
12
+ # | / | #
13
+ # \ _, / #
14
+ # \__.__/ #
15
+ # #
16
+ ################################################################################
17
+ # This program is free software: you can redistribute it #
18
+ # and/or modify it under the terms of the GNU General Public License #
19
+ # as published by the Free Software Foundation, #
20
+ # either version 3 of the License, or (at your option) any later version. #
21
+ # #
22
+ # Commercial licensing may be available for a fee under a different license. #
23
+ ################################################################################
24
+ # This program is distributed in the hope that it will be useful, #
25
+ # but WITHOUT ANY WARRANTY; #
26
+ # without even the implied warranty of MERCHANTABILITY #
27
+ # or FITNESS FOR A PARTICULAR PURPOSE. #
28
+ # See the GNU General Public License for more details. #
29
+ # #
30
+ # You should have received a copy of the GNU General Public License #
31
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
32
+ ################################################################################
33
+ #++
34
+
35
+ # Contains methods for returning output from console commands.
36
+ module ExecuteShell
37
+
38
+ # Returns output from a console command.
39
+ # ==== Input
40
+ # [command : String] The command to be run.
41
+ # [path : String : Dir.getwd] The path to use for the command.
42
+ # ==== Output
43
+ # [Boolean] Whether the shell execution was successful.
44
+ # [String]
45
+ def shell(command, path = Dir.getwd)
46
+ raise_not_implemented(__method__) unless
47
+ [:linux, :mingw].include?(Platform::IMPL)
48
+
49
+ out = ''
50
+ err = ''
51
+ success = nil
52
+
53
+ path ||= Dir.getwd
54
+
55
+ begin
56
+ STDOUT.puts command if ScriptEnv.development
57
+
58
+ block = case Platform::IMPL
59
+ when :mingw
60
+ lambda {
61
+ # Run the command and wait for it to execute.
62
+ result = Open3::popen3('cmd') do |std_in, std_out, std_err, thread|
63
+ # Set up the command.
64
+ std_in.puts command
65
+
66
+ # Run it.
67
+ std_in.close
68
+
69
+ # Get the output.
70
+ out = std_out.read.strip
71
+ err = std_err.read.strip
72
+ end
73
+ }
74
+ when :linux
75
+ lambda {
76
+ # Run the command and wait for it to execute.
77
+ result = Open4::popen4('bash') do |pid, std_in, std_out, std_err|
78
+ # Set up the command.
79
+ std_in.puts command
80
+
81
+ # Run it.
82
+ std_in.close
83
+
84
+ # Get the output.
85
+ out = std_out.read.strip
86
+ err = std_err.read.strip
87
+ end
88
+ }
89
+ end
90
+
91
+ wrap_success, wrap_out = wrap_path(path, block)
92
+
93
+ # Success is determined by lack of error text.
94
+ success = err.empty?
95
+
96
+ # Remove all the extra stuff that the cmd prompt adds.
97
+ if Platform::IMPL == :mingw
98
+ out.gsub!(/\n\n#{path.gsub(%r[/], '\\\\\\')}>\Z/, '')
99
+
100
+ # replace contains the command line prompt
101
+ # and the command up to the first space.
102
+ replace = path.gsub(%r[/], '\\\\\\')
103
+ replace += '>'
104
+ replace += command[0..(command.index(/ /) || 0) - 1]
105
+
106
+ # Remove the header portion of the text.
107
+ # This includes the Microsoft 'banner' text
108
+ # that consumes the first two lines.
109
+ out = out.gsub(/\A.+#{replace}.*?$/m, '').strip
110
+ end
111
+
112
+ # Set the error flag and notify the user if anything went wrong.
113
+ out = (out + "\n" + err).strip unless success
114
+ out += wrap_out.strip unless wrap_success
115
+
116
+ # Include the wrapper's success flag in the resulting success flag.
117
+ success = success && wrap_success
118
+ rescue Exception => exc
119
+ # Format exception messages.
120
+ success = false
121
+ out += format_error(exc)
122
+ end
123
+
124
+ return success, out
125
+ end
126
+
127
+ ############################################################################
128
+ private
129
+ ############################################################################
130
+
131
+ # Formats error messages.
132
+ # ==== Input
133
+ # [exception : Exception] The error that was raised.
134
+ # ==== Output
135
+ # [String] The error information in a readable format.
136
+ def format_error(exception)
137
+ error_format = '%s: %s%s'
138
+ error_format % [exception.class, exception.message,
139
+ ScriptEnv.development ? "\n#{exception.backtrace.join("\n")}" : '']
140
+ end
141
+
142
+ # Raises a NotImplementedError.
143
+ # ==== Input
144
+ # [method : String] The method that does not have
145
+ # an implementation for the current platform.
146
+ def raise_not_implemented(method)
147
+ raise NotImplementedError,
148
+ "#{method} " +
149
+ "has not been implemented for #{Platform::IMPL}."
150
+ end
151
+
152
+ # Runs a block of code by changing the path first.
153
+ # ==== Input
154
+ # [path : String] The path to change to prior to running the block.
155
+ # [block : Proc] The code block to execute.
156
+ # [*args : Array] Any other parameters
157
+ # that will be passed on to <tt>block</tt>.
158
+ # ==== Output
159
+ # [Boolean] Indicates whether the block was executed successfully.
160
+ # [String] Any error messages from trapped errors.
161
+ def wrap_path(path, block, *args)
162
+ path ||= File.expand_path(Dir.getwd)
163
+ original = File.expand_path(Dir.getwd)
164
+ out = nil
165
+
166
+ STDOUT.puts path if ScriptEnv.development
167
+
168
+ begin
169
+ Dir.chdir path unless path == original
170
+ block.call(*args)
171
+ rescue Exception => exc
172
+ # Format exception messages.
173
+ out = format_error(exc)
174
+ ensure
175
+ Dir.chdir original unless path == original
176
+ end
177
+
178
+ return out.nil?, out
179
+ end
180
+ end
@@ -0,0 +1,81 @@
1
+ # This file contains a class to manage information about the environment.
2
+
3
+ #--
4
+ ################################################################################
5
+ # Copyright (C) 2011 Travis Herrick #
6
+ ################################################################################
7
+ # #
8
+ # \v^V,^!v\^/ #
9
+ # ~% %~ #
10
+ # { _ _ } #
11
+ # ( * - ) #
12
+ # | / | #
13
+ # \ _, / #
14
+ # \__.__/ #
15
+ # #
16
+ ################################################################################
17
+ # This program is free software: you can redistribute it #
18
+ # and/or modify it under the terms of the GNU General Public License #
19
+ # as published by the Free Software Foundation, #
20
+ # either version 3 of the License, or (at your option) any later version. #
21
+ # #
22
+ # Commercial licensing may be available for a fee under a different license. #
23
+ ################################################################################
24
+ # This program is distributed in the hope that it will be useful, #
25
+ # but WITHOUT ANY WARRANTY; #
26
+ # without even the implied warranty of MERCHANTABILITY #
27
+ # or FITNESS FOR A PARTICULAR PURPOSE. #
28
+ # See the GNU General Public License for more details. #
29
+ # #
30
+ # You should have received a copy of the GNU General Public License #
31
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
32
+ ################################################################################
33
+ #++
34
+
35
+ # This class manages environment information.
36
+ class ScriptEnv
37
+ # Contains valid environments for the script.
38
+ STATES = {
39
+ :development => :development,
40
+ :test => :testing,
41
+ :production => :production,
42
+ }
43
+
44
+ # Indicates the current environment of the script.
45
+ @@env = nil
46
+
47
+ class << self
48
+ # Retrieves the current environment setting.
49
+ def env
50
+ if @@env.nil? && File.expand_path(Dir.getwd) ==
51
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
52
+ return STATES[:development]
53
+ else
54
+ return STATES[@@env] || STATES[:production]
55
+ end
56
+ end
57
+
58
+ # Black magic.
59
+ #
60
+ # Allows the getting and setting of the 'state' of the script.
61
+ # Only one environment can be active at a time.
62
+ # Setting one environment to true means the others are false.
63
+ # ==== Input
64
+ # [method : Symbol] The method that was called.
65
+ # [*args : Array] Any arguments that were passed in.
66
+ # [&block : Block] A block, if specified.
67
+ def method_missing(method, *args, &block)
68
+ if STATES.keys.include?(method.to_s.sub(/=$/, '').to_sym)
69
+ if method.to_s.match(/=$/) # Setter method.
70
+ value = args && args.shift || nil
71
+ @@env = method.to_s[0..-2].to_sym if value == true
72
+ else # ------------------- # Getter method.
73
+ states = STATES.select { |k, v| v == env }
74
+ return !states[method].nil?
75
+ end
76
+ else
77
+ super
78
+ end
79
+ end
80
+ end
81
+ end
data/rakefile ADDED
@@ -0,0 +1,121 @@
1
+ root_path = File.expand_path(File.dirname(__FILE__))
2
+ require 'bundler'
3
+ Bundler.require :rake
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/clean'
7
+ require_relative 'lib/execute_shell'
8
+
9
+ include ExecuteShell
10
+
11
+ task :default => [:tests]
12
+
13
+ # Include any ruby files in the tasks folder.
14
+ task_files = Dir[
15
+ File.join(File.expand_path(File.dirname(__FILE__)), 'tasks', '*.rb')]
16
+
17
+ task_files.each do |rake_file|
18
+ require rake_file
19
+ end
20
+
21
+ # Add a task to run all tests.
22
+ Rake::TestTask.new('tests') do |task|
23
+ task.pattern = 'test/*_test.rb'
24
+ task.verbose = true
25
+ task.warning = true
26
+ end
27
+ Rake::Task[:tests].comment = 'Run all tests'
28
+
29
+ ################################################################################
30
+ namespace :test do
31
+ ################################################################################
32
+ file_list = Dir['test/*_test.rb']
33
+
34
+ # Add a distinct test task for each test file.
35
+ file_list.each do |item|
36
+ # Get the name to use for the task by removing '_test.rb' from the name.
37
+ task_name = File.basename(item, '.rb').gsub(/_test$/, '')
38
+
39
+ # Add each test.
40
+ Rake::TestTask.new(task_name) do |task|
41
+ task.pattern = item
42
+ task.verbose = true
43
+ task.warning = true
44
+ end
45
+ end
46
+ ################################################################################
47
+ end # :test
48
+ ################################################################################
49
+
50
+ ################################################################################
51
+ namespace :rdoc do
52
+ ################################################################################
53
+
54
+ # Set the paths used by each of the rdoc options.
55
+ RDOC_FILES = {
56
+ :all => ['**/*.rb'],
57
+ :test => ['test/lib/**/*.rb'],
58
+ :app => [
59
+ '*.rb',
60
+ 'lib/**/*.rb',
61
+ ],
62
+ }
63
+
64
+ # Loop through the typs of rdoc files to generate an rdoc task for each one.
65
+ RDOC_FILES.keys.each do |rdoc_task|
66
+ Rake::RDocTask.new(
67
+ :rdoc => rdoc_task,
68
+ :clobber_rdoc => "#{rdoc_task}:clobber",
69
+ :rerdoc => "#{rdoc_task}:force") do |rdtask|
70
+ rdtask.rdoc_dir = "help/#{rdoc_task}"
71
+ rdtask.options << '--charset' << 'utf8'
72
+ rdtask.rdoc_files.include(RDOC_FILES[rdoc_task])
73
+ end
74
+
75
+ Rake::Task[rdoc_task].comment =
76
+ "Generate #{rdoc_task} RDoc documentation."
77
+ end
78
+ ################################################################################
79
+ end # :rdoc
80
+ ################################################################################
81
+
82
+ desc 'Search for a string in all files using a case insensitive search.'
83
+ task :grep, [:text] do |task, args|
84
+ # Set default search text to t-o-d-o.
85
+ # It is done this way to prevent coming up in the search itself.
86
+ args.with_defaults(:text => 't' + 'o' + 'd' + 'o')
87
+
88
+ # Use the sample color scheme, since it provides us with bold red via :error.
89
+ HighLine.color_scheme = HighLine::SampleColorScheme.new
90
+ COLOR = "<%%= color('%s', :error) %%>"
91
+
92
+ notification = "\nSearching for '%s':\n\n"
93
+
94
+ # Output the text that is being searched for.
95
+ case Platform::IMPL
96
+ when :linux
97
+ notification = notification % [COLOR % args[:text]]
98
+ when :mingw
99
+ notification = notification % args[:text]
100
+ else
101
+ raise_not_implemented('grep')
102
+ end
103
+
104
+ say notification
105
+
106
+ command = "grep #{args[:text]} * -ri"
107
+ success, output = shell(command)
108
+
109
+ # Send the results to the console.
110
+ case Platform::IMPL
111
+ when :linux
112
+ output = output.gsub(/(#{args[:text]})/i, COLOR % '\1')
113
+ when :mingw
114
+ else
115
+ raise_not_implemented('grep')
116
+ end
117
+
118
+ say output
119
+ end
120
+
121
+ CLOBBER.include('help')
@@ -0,0 +1,126 @@
1
+ #--
2
+ ################################################################################
3
+ # Copyright (C) 2011 Travis Herrick #
4
+ ################################################################################
5
+ # #
6
+ # \v^V,^!v\^/ #
7
+ # ~% %~ #
8
+ # { _ _ } #
9
+ # ( * - ) #
10
+ # | / | #
11
+ # \ _, / #
12
+ # \__.__/ #
13
+ # #
14
+ ################################################################################
15
+ # This program is free software: you can redistribute it #
16
+ # and/or modify it under the terms of the GNU General Public License #
17
+ # as published by the Free Software Foundation, #
18
+ # either version 3 of the License, or (at your option) any later version. #
19
+ # #
20
+ # Commercial licensing may be available for a fee under a different license. #
21
+ ################################################################################
22
+ # This program is distributed in the hope that it will be useful, #
23
+ # but WITHOUT ANY WARRANTY; #
24
+ # without even the implied warranty of MERCHANTABILITY #
25
+ # or FITNESS FOR A PARTICULAR PURPOSE. #
26
+ # See the GNU General Public License for more details. #
27
+ # #
28
+ # You should have received a copy of the GNU General Public License #
29
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
30
+ ################################################################################
31
+ #++
32
+
33
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'require')
34
+
35
+ class ExecuteShellTest < Test::Unit::TestCase
36
+ def setup
37
+ @obj = Object.new
38
+ @obj.extend(@class)
39
+
40
+ @working_path = Dir.getwd
41
+ @home_path = File.expand_path(File.join('~', '..'))
42
+ @user = File.basename(File.expand_path('~'))
43
+
44
+ Dir.chdir @home_path
45
+
46
+ # Setting $stderr to STDERR does not work due to the way
47
+ # STDERR was being redirected (or something).
48
+ @stdout_inspect = $stdout.inspect
49
+ @stderr_inspect = $stderr.inspect
50
+ end
51
+
52
+ def teardown
53
+ Dir.chdir @working_path
54
+ assert_equal(@stdout_inspect, $stdout.inspect)
55
+ assert_equal(@stderr_inspect, $stderr.inspect)
56
+ end
57
+
58
+ def test_stdout
59
+ command = nil
60
+
61
+ case Platform::IMPL
62
+ when :linux
63
+ command = "ls #{@home_path}"
64
+ when :mingw
65
+ command = "dir #{convert_to_backslash(@home_path)}"
66
+ else
67
+ raise_not_implemented
68
+ end
69
+
70
+ success, output = @obj.shell(command)
71
+
72
+ assert output.index(@user),
73
+ "'#{command}' does not appear to include " +
74
+ "the current user's folder (#{@user})."
75
+ assert_true success, "#{command} was not successful"
76
+ end
77
+
78
+ def test_stderr
79
+ command = nil
80
+ result = nil
81
+
82
+ folder = 'giggidygiggidy'
83
+
84
+ test_path = File.join(@home_path, folder)
85
+
86
+ case Platform::IMPL
87
+ when :linux
88
+ command = "ls #{test_path}"
89
+ result = "ls: cannot access #{test_path}: No such file or directory"
90
+ when :mingw
91
+ command = "dir #{convert_to_backslash(test_path)}"
92
+ result = 'File Not Found'
93
+ else
94
+ raise_not_implemented
95
+ end
96
+
97
+ success, output = @obj.shell(command)
98
+
99
+ assert_equal result, output if Platform::IMPL == :linux
100
+
101
+ assert output.index(result)
102
+ assert_false success
103
+ end
104
+
105
+ def test_grep
106
+ Dir.chdir @working_path
107
+ command = 'grep jabberwocky' + 'riseagain * -ri'
108
+ result = nil
109
+
110
+ assert_equal [true, ''], @obj.shell(command)
111
+ end
112
+
113
+ ############################################################################
114
+ private
115
+ ############################################################################
116
+
117
+ def convert_to_backslash(text)
118
+ text.gsub(%r[/], "\\")
119
+ end
120
+
121
+ def raise_not_implemented(method)
122
+ raise NotImplementedError,
123
+ "#{method} " +
124
+ "has not been implemented for #{Platform::IMPL}."
125
+ end
126
+ end
@@ -0,0 +1,502 @@
1
+ # This file contains a monkey patch of Test::Unit::TestCase.
2
+
3
+ #--
4
+ ################################################################################
5
+ # Copyright (C) 2011 Travis Herrick #
6
+ ################################################################################
7
+ # #
8
+ # \v^V,^!v\^/ #
9
+ # ~% %~ #
10
+ # { _ _ } #
11
+ # ( * - ) #
12
+ # | / | #
13
+ # \ _, / #
14
+ # \__.__/ #
15
+ # #
16
+ ################################################################################
17
+ # This program is free software: you can redistribute it #
18
+ # and/or modify it under the terms of the GNU General Public License #
19
+ # as published by the Free Software Foundation, #
20
+ # either version 3 of the License, or (at your option) any later version. #
21
+ # #
22
+ # Commercial licensing may be available for a fee under a different license. #
23
+ ################################################################################
24
+ # This program is distributed in the hope that it will be useful, #
25
+ # but WITHOUT ANY WARRANTY; #
26
+ # without even the implied warranty of MERCHANTABILITY #
27
+ # or FITNESS FOR A PARTICULAR PURPOSE. #
28
+ # See the GNU General Public License for more details. #
29
+ # #
30
+ # You should have received a copy of the GNU General Public License #
31
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
32
+ ################################################################################
33
+ #++
34
+
35
+ # Monkey patch Test::Unit::TestCase to make it do cool stuff.
36
+ Test::Unit::TestCase.class_eval do
37
+ # Since this is in a class_eval, instance methods need to be wrapped up
38
+ # in class_variable_set or ruby will throw warnings.
39
+
40
+ # Indicates whether the class has already been initialized.
41
+ # This combined with @@class_name prevents duplicate patching.
42
+ class_variable_set(:@@initialized, false)
43
+
44
+ # Keeps track of the class that has most recently been initialized.
45
+ # This combined with @@initialized prevents duplicate patching.
46
+ class_variable_set(:@@class_name, '')
47
+
48
+ # Initializes the class
49
+ # and exposes private methods and variables of the class that is being tested.
50
+ def initialize(*args)
51
+ # Call initialize on the superclass.
52
+ super
53
+
54
+ @obj = nil
55
+
56
+ reset_io
57
+ reset_trace
58
+
59
+ # This block ensures that tests still work if there is not a class that
60
+ # corresponds with the test file/class.
61
+ @class = nil
62
+ begin
63
+ # Get the class that is being tested.
64
+ # Assume that the name of the class is found by removing 'Test'
65
+ # from the test class.
66
+ @class = Kernel.const_get(self.class.name.gsub(/Test$/, ''))
67
+ @@initialized = ((@class.name == @@class_name) && @@initialized)
68
+ @@class_name = @class.name
69
+ rescue
70
+ @@initialized = true
71
+ @@class_name = ''
72
+ end
73
+
74
+ # Only patch if this code has not yet been run.
75
+ if !@@initialized and @class.class.name != 'Module'
76
+ set_instance_method_wrappers
77
+
78
+ # Expose private class methods.
79
+ # We will only expose the methods we are responsible for creating.
80
+ # (i.e. subtracting the superclass's private methods)
81
+ expose_private_methods(:class,
82
+ @class.private_methods -
83
+ @class.superclass.private_methods)
84
+
85
+ # Expose private instance methods.
86
+ # We will only expose the methods we are responsible for creating.
87
+ # (i.e. subtracting the superclass's private methods)
88
+ expose_private_methods(:instance,
89
+ @class.private_instance_methods -
90
+ @class.superclass.private_instance_methods)
91
+
92
+ # Expose variables.
93
+ # Requires that variables are assigned to in the constructor.
94
+ wrap_output {
95
+ expose_variables(@class.class_variables +
96
+ @class.new([]).instance_variables)
97
+ }
98
+
99
+ # Indicate that this code has been run.
100
+ @@initialized = true
101
+ end
102
+ end
103
+
104
+ # Sets up functionality for all tests.
105
+ #
106
+ # Tracing is set up here so that it is only running during tests.
107
+ #
108
+ # If you want to disable tracing, simply override the setup method
109
+ # without calling super. (It would be good form to also override teardown).
110
+ def setup
111
+ set_trace_func proc { |event, file, line, id, binding, class_name|
112
+ if class_name == @class and
113
+ @stack_trace.last != {:class => class_name.name, :method => id}
114
+ @stack_trace << {
115
+ :class => class_name.name,
116
+ :method => id,
117
+ }
118
+ end
119
+ }
120
+ end
121
+
122
+ # Clean up after each test.
123
+ #
124
+ # If you disable tracing, it would be good form to override this method
125
+ # as well without calling super.
126
+ def teardown
127
+ set_trace_func nil
128
+ end
129
+
130
+ ############################################################################
131
+ private
132
+ ############################################################################
133
+
134
+ ############################################################################
135
+ # Monkey patching methods for the class being tested.
136
+ ############################################################################
137
+
138
+ # Monkey patch the class's initializer to enable tracing
139
+ # with parameters and results.
140
+ def set_initializer
141
+ @class.class_eval do
142
+ attr_accessor :trace
143
+
144
+ alias :test_case_initialize :initialize
145
+ def initialize(*args)
146
+ @trace = []
147
+ result = test_case_initialize(*args)
148
+ return result
149
+ end
150
+ end
151
+ end
152
+
153
+ # Loop through the instance methods, calling set_instance_methods for each.
154
+ def set_instance_method_wrappers
155
+ [
156
+ :public_instance_methods,
157
+ :protected_instance_methods,
158
+ :private_instance_methods
159
+ ].each do |method_id|
160
+
161
+ scope = method_id.to_s.gsub(/_.*/, '')
162
+
163
+ set_instance_methods(@class.send(method_id) -
164
+ @class.superclass.send(method_id), scope)
165
+ end
166
+
167
+ # If this is not at the end, the loop will attempt to do it's thing
168
+ # with the constructor created in this method, which is not necessary.
169
+ set_initializer
170
+ end
171
+
172
+ # Loop through the list of methods that are passed in,
173
+ # creating a wrapper method that enables tracing.
174
+ #
175
+ # Tracing data includes method name, parameters, and result.
176
+ # ==== Input
177
+ # [method_list : Array] A list of methods that will have wrapping functions
178
+ # created to enable tracing.
179
+ # [scope : String] The scope of the original function.
180
+ def set_instance_methods(method_list, scope)
181
+ method_list.each do |method_id|
182
+ # Setters and methods that accept blocks do not appear to work.
183
+ next if method_id =~ /=/ or method_id =~ /wrap_output/
184
+
185
+ # Build the method.
186
+ new_method = <<-DOC
187
+ alias :test_case_#{method_id} :#{method_id}
188
+ def #{method_id}(*args)
189
+ result = test_case_#{method_id}(*args)
190
+ @trace << {
191
+ :method => '#{method_id}',
192
+ :args => args,
193
+ :result => result
194
+ }
195
+ return result
196
+ end
197
+ #{scope} :#{method_id}
198
+ DOC
199
+
200
+ # Add the method to the class.
201
+ @class.class_eval do
202
+ eval(new_method)
203
+ end
204
+ end
205
+ end
206
+
207
+ # Expose the private methods that are passed in. New methods will be created
208
+ # with the old method name followed by '_public_test'. If the original
209
+ # method contained a '?', it will be removed in the new method.
210
+ # ==== Input
211
+ # [type : Symbol] Indicates whether to handle instance or class methods.
212
+ #
213
+ # Only :class and :instance are supported.
214
+ # [methods : Array] An array of the methods to expose.
215
+ def expose_private_methods(type, methods)
216
+ # Get the text that the method should be wrapped in.
217
+ method_wrapper = wrapper(type)
218
+
219
+ # Loop through the methods.
220
+ methods.each do |method|
221
+ # Remove ?s.
222
+ new_method = method.to_s.gsub(/\?/, '')
223
+
224
+ # This is the new method.
225
+ new_method = <<-DOC
226
+ def #{new_method}_public_test(*args)
227
+ #{method}(*args)
228
+ end
229
+ DOC
230
+
231
+ # Add the wrapping text.
232
+ new_method = method_wrapper % [new_method]
233
+
234
+ # Add the method to the class.
235
+ @class.class_eval do
236
+ eval(new_method)
237
+ end
238
+ end
239
+ end
240
+
241
+ # Expose the variables.
242
+ #
243
+ # New methods will be created (a getter and a setter) for each variable.
244
+ #
245
+ # Regardless of the type of variable, these methods are only available
246
+ # via an instance.
247
+ # ==== Input
248
+ # [variables : Array] An array of variables to expose.
249
+ def expose_variables(variables)
250
+ # Get the text that the methods should be wrapped in.
251
+ var_wrapper = wrapper(:instance)
252
+
253
+ # Loop through the variables
254
+ variables.each do |var|
255
+ # Remove any @s.
256
+ new_method = var.to_s.gsub(/@/, '')
257
+
258
+ # These are the new getter and setters.
259
+ new_method = <<-DOC
260
+ def #{new_method}_variable_method
261
+ #{var}
262
+ end
263
+
264
+ def #{new_method}_variable_method=(value)
265
+ #{var} = value
266
+ end
267
+ DOC
268
+
269
+ # Add the wrapping text.
270
+ new_method = var_wrapper % [new_method]
271
+
272
+ # Add the methods to the class.
273
+ @class.class_eval do
274
+ eval(new_method)
275
+ end
276
+ end
277
+ end
278
+
279
+ # Returns the wrapping text for the specified type of method.
280
+ # ==== Input
281
+ # [type : Symbol] Indicates whether to handle instance or class methods.
282
+ #
283
+ # Only :class & :instance are supported.
284
+ # ==== Output
285
+ # [String] The text that the specified type of method should be wrapped in.
286
+ def wrapper(type)
287
+ case type
288
+ when :class then 'class << self;%s;end'
289
+ when :instance then '%s'
290
+ end
291
+ end
292
+
293
+ ############################################################################
294
+ # I/O support methods.
295
+ ############################################################################
296
+
297
+ # Return the actual output to stdout and stderr.
298
+ # ==== Output
299
+ # [Array] Two element array of strings.
300
+ #
301
+ # The first element is from stdout.
302
+ #
303
+ # The second element is from stderr.
304
+ def real_finis
305
+ return out, err
306
+ end
307
+
308
+ # Wrap a block to capture the output to stdout and stderr.
309
+ # ==== Input
310
+ # [&block : Block] The block of code that will have stdout and stderr trapped.
311
+ def wrap_output(&block)
312
+ begin
313
+ $stdout = @out
314
+ $stderr = @err
315
+ yield
316
+ ensure
317
+ $stdout = STDOUT
318
+ $stderr = STDERR
319
+ end
320
+ end
321
+
322
+ # Returns the output from stdout as a string.
323
+ # ==== Output
324
+ # [String] The output from stdout.
325
+ #
326
+ # All trailing line feeds are removed.
327
+ def out
328
+ @out.respond_to?(:string) ? @out.string.gsub(/\n*\z/, '') : ''
329
+ end
330
+
331
+ # Returns the output from stderr as a string.
332
+ # ==== Output
333
+ # [String] The output from stderr.
334
+ #
335
+ # All trailing line feeds are removed.
336
+ def err
337
+ @err.respond_to?(:string) ? @err.string.gsub(/\n*\z/, '') : ''
338
+ end
339
+
340
+ # Reset the stdout and stderr stream variables.
341
+ def reset_io
342
+ @out = StringIO.new
343
+ @err = StringIO.new
344
+ end
345
+
346
+ ############################################################################
347
+ # Support methods.
348
+ ############################################################################
349
+
350
+ # Indicates whether the specified method has been called on a given class.
351
+ # ==== Input
352
+ # [method_name : String] The name of the method.
353
+ #
354
+ # This value may be a string or a symbol.
355
+ # [class_name : String : @class.name] The name of the class that the method
356
+ # should have been invoked from.
357
+ def method_called?(method_name, class_name = @class.name)
358
+ !@stack_trace.index(
359
+ {:method => method_name.to_sym, :class => class_name}).nil?
360
+ end
361
+
362
+ # Resets the trace arrays.
363
+ #
364
+ # This is intended for use in cases where code may be called multiple
365
+ # times in a single test.
366
+ def reset_trace
367
+ @stack_trace = []
368
+ @obj.trace = [] if @obj.respond_to?(:trace=)
369
+ end
370
+
371
+ # Shows the trace history as it stands, if the object supports it.
372
+ def show_trace
373
+ return unless defined? @obj
374
+ puts @obj.trace.join("\n" + '-' * 80 + "\n") if @obj.respond_to?(:trace)
375
+ end
376
+
377
+ ############################################################################
378
+ # Assertions.
379
+ ############################################################################
380
+
381
+ # Asserts that a value is equal to false.
382
+ # ==== Input
383
+ # [value : Any] The value to check for equality against false.
384
+ # [message : String : nil] The message to display if the value is not false.
385
+ def assert_false(value, message = nil)
386
+ assert_equal false, value, message
387
+ end
388
+
389
+ # Asserts that a value is equal to true.
390
+ # ==== Input
391
+ # [value : Any] The value to check for equality against true.
392
+ # [message : String : nil] The message to display if the value is not true.
393
+ def assert_true(value, message = nil)
394
+ assert_equal true, value, message
395
+ end
396
+
397
+ # Asserts that the negation of a value is true.
398
+ # ==== Input
399
+ # [value : Any] The value which will be negated and then asserted.
400
+ # [message : String : nil] The message to display if the assertion fails.
401
+ def assert_not(value, message = nil)
402
+ assert !value, message
403
+ end
404
+
405
+ # Assert that an array has a specified number of elements.
406
+ # ==== Input
407
+ # [array : Array] The array that will have it's length checked.
408
+ # [length : Fixnum] The length that the array should be.
409
+ # [message : String : nil] The message to display if the assertion fails.
410
+ def assert_array_count(array, length, message = nil)
411
+ if message.nil?
412
+ message = "#{array} has #{array.length} item(s), " +
413
+ "but was expected to have #{length}."
414
+ end
415
+
416
+ assert array.length == length, message
417
+ end
418
+
419
+ ############################################################################
420
+ # Assertions - Stack trace.
421
+ ############################################################################
422
+
423
+ # Asserts that a method was called on a class.
424
+ # ==== Input
425
+ # [method_name : String] The name of the method to check for.
426
+ # [class_name : String : @class.name] The name of the class
427
+ # on which <tt>method_name</tt>
428
+ # should have been invoked.
429
+ def assert_method(method_name, class_name = @class.name)
430
+ assert method_called?(method_name.to_sym, class_name),
431
+ "#{class_name}.#{method_name} has not been called."
432
+ end
433
+
434
+ # Asserts that a method was not called on a class.
435
+ # ==== Input
436
+ # [method_name : String] The name of the method to check for.
437
+ # [class_name : String : @class.name] The name of the class
438
+ # on which <tt>method_name</tt>
439
+ # should not have been invoked.
440
+ def assert_not_method(method_name, class_name = @class.name)
441
+ assert !method_called?(method_name.to_sym, class_name),
442
+ "#{class_name}.#{method_name} should not be called."
443
+ end
444
+
445
+ # Asserts that a method was called with the specified parameters.
446
+ # ==== Input
447
+ # [method_name : String] The name of the method to check.
448
+ # [*args : Array] The parameters that were passed in to the method.
449
+ def assert_trace_args(method_name, *args)
450
+ match = false
451
+
452
+ list = []
453
+
454
+ # Loop through the stack trace to see if the method was called
455
+ # with the specified arguments.
456
+ @obj.trace.each do |trace|
457
+ if trace[:method] == method_name and trace[:args] == args
458
+ match = true
459
+ break
460
+ elsif trace[:method] == method_name
461
+ list << trace[:args]
462
+ end
463
+ end
464
+
465
+ assert match,
466
+ "#{method_name} was not called with the following parameters:\n" +
467
+ "#{args.join("\n" + '-' * 80 + "\n")}\n" +
468
+ '*' * 80 + "\n" +
469
+ "#{method_name} was recorded as follows:\n" +
470
+ "#{list.join("\n" + '-' * 80 + "\n")}"
471
+ end
472
+
473
+ # Asserts that a method was called with the specified parameters
474
+ # and returned the specified result.
475
+ # ==== Input
476
+ # [method_name : String] The name of the method to check.
477
+ # [result : Any] The expected result of the method call.
478
+ # [*args : Array] The parameters that were passed in to the method.
479
+ def assert_trace_info(method_name, result, *args)
480
+ match = (@obj.trace.index(
481
+ {:methd => method_name, :args => args, :result => result}))
482
+
483
+ list = []
484
+
485
+ # Only get a list of possible results if a match was not found.
486
+ unless match
487
+ @obj.trace.each do |trace|
488
+ if trace[:method] == method_name
489
+ list << {:args => trace[:args], :result => trace[:result]}
490
+ end
491
+ end
492
+ end
493
+
494
+ assert match,
495
+ "#{method_name} was not called with the following parameters:\n" +
496
+ "#{args}\n" +
497
+ "or did not return the following result:\n" +
498
+ "#{result}\n" +
499
+ "#{method_name} was recorded as follows:\n" +
500
+ "#{list.join("\n" + '-' * 80 + "\n")}"
501
+ end
502
+ end
data/test/require.rb ADDED
@@ -0,0 +1,13 @@
1
+ root_path = File.join(File.dirname(File.expand_path(__FILE__)), '..')
2
+ root_path = File.expand_path(root_path)
3
+
4
+ Bundler.require :test
5
+
6
+ require 'test/unit'
7
+
8
+ require_relative '../lib/execute_shell'
9
+
10
+ file_list = Dir[File.join(root_path, 'test', 'lib', '*.rb')]
11
+ file_list.each do |file|
12
+ require file
13
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: execute_shell
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Travis Herrick
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-07-28 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: Platform
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 4
31
+ - 0
32
+ version: 0.4.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: open4
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 1
47
+ version: 1.0.1
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: win32-open3-19
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 0
61
+ - 1
62
+ version: 0.0.1
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rake
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - "="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ - 8
76
+ - 7
77
+ version: 0.8.7
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: highline
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 1
90
+ - 6
91
+ - 1
92
+ version: 1.6.1
93
+ type: :development
94
+ version_requirements: *id005
95
+ description:
96
+ email: tthetoad@gmail.com
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files:
102
+ - README
103
+ files:
104
+ - lib/execute_shell/execute_shell.rb
105
+ - lib/execute_shell/script_env.rb
106
+ - lib/execute_shell.rb
107
+ - Gemfile.lock
108
+ - README
109
+ - gemfile
110
+ - rakefile
111
+ - execute_shell.gemspec
112
+ - test/require.rb
113
+ - test/lib/test_case.rb
114
+ - test/execute_shell_test.rb
115
+ has_rdoc: true
116
+ homepage:
117
+ licenses:
118
+ - MIT
119
+ post_install_message:
120
+ rdoc_options: []
121
+
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ requirements: []
141
+
142
+ rubyforge_project:
143
+ rubygems_version: 1.3.7
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: Executes shell commands.
147
+ test_files:
148
+ - test/require.rb
149
+ - test/lib/test_case.rb
150
+ - test/execute_shell_test.rb