executor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in executor.gemspec
4
+ gemspec
@@ -0,0 +1,42 @@
1
+ # Executor
2
+
3
+ Executor aims to provide a standard interface to work with binaries outside of ruby. It provides a standard implementation of the following requirements:
4
+
5
+ * Capturing exit status and raising appropriate exception
6
+ * Redirecting stderr
7
+ * Asynchronous callbacks
8
+ * Logging of command output
9
+
10
+ ## Options
11
+
12
+ * redirect_stderr - This will cause any stderr from a command to be merged to stdout
13
+ * raise_exceptions - This will listen for the exit code of the executed process and raise an exception if non 0
14
+ * async - This is true by default if a block is given to Executor::command, async will create a new thread and execute the passed block upon completion of the process
15
+
16
+ * logger - This takes an instance of logger (or an instance that implements #info)
17
+
18
+ ## Usage
19
+
20
+ ### Basic
21
+
22
+ require 'executor'
23
+
24
+ Executor::command("echo 5")
25
+
26
+ ### With redirection
27
+
28
+ Executor::command("expr 5 / 0", :redirect_stderr => true)
29
+
30
+ ### With one-time configuration
31
+
32
+ Executor::configure(
33
+ :raise_exceptions => false
34
+ )
35
+
36
+ Executor::command("expr 5 / 0")
37
+
38
+ ## Special notes
39
+
40
+ * In async mode (when block given or via explicit configuration), an exception if caught will be returned to the callback
41
+
42
+ credits: me, robgleeson@freenode
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "executor/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "executor"
7
+ s.version = Executor::VERSION
8
+ s.authors = ["Thomas W. devol"]
9
+ s.email = ["vajrapani666@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Execute system commands through a configurable interface}
12
+ s.description = %q{Execute system commands with exception handling and logging}
13
+
14
+ s.rubyforge_project = "executor"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "nullobject"
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "em-spec"
24
+ end
@@ -0,0 +1,87 @@
1
+ require "executor/version"
2
+ require "nullobject"
3
+ require "timeout"
4
+
5
+ module Executor
6
+
7
+ ExecutorFailure = Class.new(StandardError)
8
+ CommandFailure = Class.new(ExecutorFailure)
9
+ TimeoutFailure = Class.new(ExecutorFailure)
10
+
11
+ class << self
12
+ def method_missing(meth, *args, &block)
13
+ if Executor.respond_to?(meth, true)
14
+ Executor.send(meth, *args, &block)
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+
21
+ class Executor
22
+ DEFAULTS = {
23
+ :redirect_stderr => false,
24
+ :raise_exceptions => true,
25
+ :async => false,
26
+ :logger => Null::Object.instance
27
+ }
28
+ class << self
29
+ attr_reader :config
30
+
31
+ def configure(config={})
32
+ @config = (@config || Executor::DEFAULTS).merge(config)
33
+ end
34
+
35
+ def command(cmd, options={}, &block)
36
+ options = configure.dup.merge(options)
37
+ options[:logger].info "COMMAND: #{cmd}"
38
+ options[:async] ||= block_given?
39
+ raise ArgumentError.new("No block given for async command") if (options[:async] && !block_given?)
40
+
41
+ if options[:async]
42
+ handle_async(cmd, options, &block)
43
+ else
44
+ return handle_nonasync(cmd, options)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def handle_nonasync(cmd, options)
51
+ stmt = ["(#{cmd})"]
52
+ stmt << "2>&1" if @config[:redirect_stderr]
53
+ if options[:timeout]
54
+ Timeout::timeout(options[:timeout]) do
55
+ output = %x{#{stmt*" "}}
56
+ end
57
+ else
58
+ output = %x{#{stmt*" "}}
59
+ end
60
+ options[:logger].info "Non-async output for #{cmd}: #{output.inspect}"
61
+ return return_or_raise(cmd, $?.success?, output, options)
62
+ rescue Timeout::Error=>e
63
+ raise TimeoutFailure.new(e)
64
+ end
65
+
66
+ def handle_async(cmd, options, &block)
67
+ Thread.new do
68
+ IO.popen(cmd) do |pipe|
69
+ output = pipe.read
70
+ options[:logger].info "Async output for #{cmd}: #{output.inspect}"
71
+ pipe.close
72
+ yield return_or_raise(cmd, $?.success?, output, options) rescue $!
73
+ end
74
+ end
75
+ end
76
+
77
+ def return_or_raise(cmd, success, output, options)
78
+ options[:logger].info "RETURN(#{cmd}): #{output}"
79
+ exception = CommandFailure.new(%Q{Command "#{cmd}" failed with #{output}})
80
+ return output if success
81
+ return exception if options[:async]
82
+ raise exception if options[:raise_exceptions]
83
+ return output
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ module Executor
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+ require 'executor'
3
+ require 'logger'
4
+
5
+ describe Executor do
6
+ let (:failing_cmd) { "expr 5 / 0" }
7
+ context "exceptions" do
8
+ it "initializes" do
9
+ expect {
10
+ raise Executor::CommandFailure.new("foo")
11
+ }.to raise_exception(Executor::CommandFailure, "foo")
12
+ end
13
+ end
14
+ context ".return_or_raise" do
15
+
16
+ let(:cmd) { "echo test"}
17
+ let(:output) { "test output" }
18
+
19
+ let(:options) { {:raise_exceptions => true, :logger=>stub(:info=>nil)} }
20
+ it "returns output if successful" do
21
+ output = "test"
22
+ Executor.return_or_raise("echo test",true, output, options).should == output
23
+ end
24
+
25
+ it "raises exceptions when configured" do
26
+ expect {
27
+ Executor.return_or_raise(cmd, false, output, options)
28
+ }.to raise_exception(Executor::CommandFailure, /#{cmd}.*#{output}/)
29
+ end
30
+
31
+ it "returns an exception if async" do
32
+ Executor.return_or_raise(cmd, false, output, options.merge(:async=>true)).should be_an(Executor::CommandFailure)
33
+ end
34
+ end
35
+ context ".command" do
36
+ context "instance configuration" do
37
+ it "overrides class-level config for a single command" do
38
+ Executor.configure(:raise_exceptions => false)
39
+ expect {
40
+ Executor.command(failing_cmd, :raise_exceptions=>true)
41
+ }.to raise_exception(Executor::CommandFailure)
42
+ end
43
+ end
44
+ context "async" do
45
+ before do
46
+ Executor.configure(
47
+ :raise_exceptions => true
48
+ )
49
+ end
50
+
51
+ it "uses a separate thread" do
52
+ i = 0
53
+ Executor.command("sleep 3 && echo 5") do |result|
54
+ i+=1
55
+ end
56
+ i.should == 0
57
+ sleep 5
58
+ i.should == 1
59
+ end
60
+
61
+ it "calls the block passed to it" do
62
+ block_called = Executor.command(failing_cmd) do |result|
63
+ true
64
+ end || false
65
+ block_called.should be_true
66
+ end
67
+ it "should raise an exception upon non zero exit" do
68
+ Executor.command(failing_cmd) do |result|
69
+ result.should be_kind_of Executor::CommandFailure
70
+ end
71
+ end
72
+ it "with explicit async config, raise exception if no block given" do
73
+ expect {
74
+ Executor.command(failing_cmd, :async=>true)
75
+ }.to raise_exception(ArgumentError)
76
+ end
77
+ end
78
+ context "redirection" do
79
+ before do
80
+ Executor.configure(
81
+ :redirect_stderr => true,
82
+ :raise_exceptions => false
83
+ )
84
+ end
85
+ it "redirects stderr" do
86
+ result = Executor.command(failing_cmd)
87
+ result.should =~ /expr: division by zero/
88
+ end
89
+ end
90
+ context "non-async" do
91
+ before do
92
+ Executor.configure(
93
+ :raise_exceptions => true
94
+ )
95
+ end
96
+ it "should raise an exception upon non zero exit" do
97
+ expect {
98
+ Executor.command(failing_cmd)
99
+ }.to raise_exception(Executor::CommandFailure)
100
+ end
101
+ end
102
+ end
103
+ context "logging" do
104
+ it "should log errors" do
105
+ logger = Logger.new(StringIO.new)
106
+ logger.should_receive(:info).with(instance_of(String)).any_number_of_times
107
+ Executor.configure(
108
+ :logger => logger,
109
+ :raise_exceptions => false
110
+ )
111
+ Executor.command("expr 5 / 0")
112
+ end
113
+ end
114
+ context "without options" do
115
+ it "should be able to execute 'echo x' " do
116
+ Executor.command(%Q{echo "x"})
117
+ end
118
+ it "should be able to execute sleep && echo " do
119
+ start = Time.now.to_i
120
+ Executor.command("sleep 2 && echo 'test'") do |result|
121
+ finish = Time.now.to_i
122
+ (finish-start).should be_greater_than(2)
123
+ end
124
+ end
125
+ end
126
+
127
+
128
+ describe "on the roadmap" do
129
+ pending "with eventmachine" do
130
+ it "uses em::popen"
131
+ end
132
+ pending "timeouts" do
133
+ it "should capture timeouts" do
134
+ expect {
135
+ Executor.command("sleep 5", :timeout=>2)
136
+ }.to raise_exception(Executor::TimeoutFailure)
137
+ end
138
+ it "should fail for timeout on async" do
139
+ Executor.command("sleep 5", :timeout => 2) do |result|
140
+ raise result
141
+ end
142
+ sleep 5
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__),'..','lib')
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__))
3
+
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: executor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas W. devol
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nullobject
16
+ requirement: &70114217923400 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70114217923400
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70114217922980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70114217922980
36
+ - !ruby/object:Gem::Dependency
37
+ name: em-spec
38
+ requirement: &70114217922560 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70114217922560
47
+ description: Execute system commands with exception handling and logging
48
+ email:
49
+ - vajrapani666@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - README.md
57
+ - Rakefile
58
+ - executor.gemspec
59
+ - lib/executor.rb
60
+ - lib/executor/version.rb
61
+ - spec/executor_spec.rb
62
+ - spec/spec_helper.rb
63
+ homepage: ''
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project: executor
83
+ rubygems_version: 1.8.10
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Execute system commands through a configurable interface
87
+ test_files:
88
+ - spec/executor_spec.rb
89
+ - spec/spec_helper.rb