execute_shell 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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