torkify 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -1
  3. data/.travis.yml +6 -0
  4. data/README.md +8 -2
  5. data/VERSION +1 -1
  6. data/lib/torkify/conductor.rb +25 -14
  7. data/lib/torkify/{events/event.rb → event/basic_event.rb} +4 -4
  8. data/lib/torkify/event/dispatcher.rb +91 -0
  9. data/lib/torkify/event/echo_event.rb +23 -0
  10. data/lib/torkify/{events/event_message.rb → event/message.rb} +2 -2
  11. data/lib/torkify/event/parser.rb +70 -0
  12. data/lib/torkify/{events → event}/pass_or_fail_event.rb +15 -3
  13. data/lib/torkify/event/ran_all_test_files_event.rb +30 -0
  14. data/lib/torkify/{events → event}/status_change_event.rb +3 -3
  15. data/lib/torkify/{events → event}/test_event.rb +4 -3
  16. data/lib/torkify/listener.rb +17 -8
  17. data/lib/torkify/log/line_matcher.rb +33 -0
  18. data/lib/torkify/log/log_reader.rb +32 -0
  19. data/lib/torkify/log/parser.rb +96 -0
  20. data/lib/torkify/log/test_error.rb +7 -0
  21. data/lib/torkify/reader.rb +7 -5
  22. data/lib/torkify/version.rb +1 -1
  23. data/lib/torkify.rb +2 -3
  24. data/spec/conductor_spec.rb +3 -4
  25. data/spec/event/basic_event_spec.rb +30 -0
  26. data/spec/event/dispatcher_spec.rb +190 -0
  27. data/spec/event/echo_event_spec.rb +22 -0
  28. data/spec/event/parser_spec.rb +288 -0
  29. data/spec/{pass_or_fail_event_spec.rb → event/pass_or_fail_event_spec.rb} +2 -2
  30. data/spec/event/ran_all_test_files_event_spec.rb +42 -0
  31. data/spec/{status_change_event_spec.rb → event/status_change_event_spec.rb} +3 -3
  32. data/spec/{test_event_spec.rb → event/test_event_spec.rb} +2 -2
  33. data/spec/log/integration_spec.rb +222 -0
  34. data/spec/log/line_matcher_spec.rb +247 -0
  35. data/spec/log/log_reader_spec.rb +54 -0
  36. data/spec/log/logs/invalid_ruby_error_1.log +1 -0
  37. data/spec/log/logs/rspec_failure_1.log +19 -0
  38. data/spec/log/logs/rspec_failure_2.log +57 -0
  39. data/spec/log/logs/ruby_error_1.log +16 -0
  40. data/spec/log/logs/ruby_error_2.log +17 -0
  41. data/spec/log/logs/ruby_error_3.log +18 -0
  42. data/spec/log/logs/test_unit_error_1.log +22 -0
  43. data/spec/log/logs/test_unit_failure_1.log +22 -0
  44. data/spec/log/test_error_spec.rb +36 -0
  45. data/torkify.gemspec +1 -1
  46. metadata +63 -40
  47. data/lib/torkify/event_parser.rb +0 -36
  48. data/lib/torkify/observer_set.rb +0 -62
  49. data/spec/event_parser_spec.rb +0 -154
  50. data/spec/event_spec.rb +0 -18
  51. data/spec/observer_set_spec.rb +0 -100
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d3a7e8564007cfc37702bcc37cbba490154c0fcd
4
+ data.tar.gz: e757b6552cb0cdb63434839d22c3ab9395759f5e
5
+ SHA512:
6
+ metadata.gz: 3059816687b7ae835341dd7100ed6bcba3c1f7fd4c6465f8010a3179839c7e552d1897b8bebf3eb85f3f21775d5f562d4c0eea4f72aae56cb257524eda060b46
7
+ data.tar.gz: 9f13edf48f04710e380723f5b9c43ec4f596bdb2b8d730dca33163b6eae93ec09cf3d70cdb74001c078b37779802add673bc8e33d8feb76faa1e0acc2589f119
data/.gitignore CHANGED
@@ -16,4 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  *.tags
19
- log/
19
+ /log/
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 2.0.0
6
+ - rbx-19mode
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # Torkify
2
2
 
3
- Torkify integrates with [tork][1], which is a solution for automating tests as you change your source files.
3
+ [![Build Status](https://travis-ci.org/joonty/torkify.png?branch=master)](https://travis-ci.org/joonty/torkify)
4
4
 
5
- Torkify hooks in to tork's remote events, and allows you to write ruby code that's called when particular events fire. This makes it easy for you to write code that triggers cool stuff when your tests pass or fail.
5
+ Torkify aims to be a one-stop shop for testing ruby applications, and handling callbacks after test execution for things like notifications.
6
+
7
+ Torkify integrates with [tork][1], which is a solution for automating test execution, as you change your source files.
8
+
9
+ Torkify hooks in to tork's remote events, and allows you to add gems and build callbacks that run when tests fail or pass. This makes it easy for you to write code that triggers cool stuff when your tests explode (your imagination is the limit).
10
+
11
+ Plus, [tork][1] is a fantastic tool that makes it very easy to run tests immediately and automatically in a pre-loaded environment.
6
12
 
7
13
  ## An example
8
14
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -1,33 +1,44 @@
1
- require_relative 'events/event'
2
- require_relative 'event_parser'
1
+ require_relative 'event/basic_event'
2
+ require_relative 'event/parser'
3
+ require_relative 'event/dispatcher'
4
+ require_relative 'log/parser'
3
5
 
4
6
  module Torkify
5
7
 
6
8
  # Connect the socket reader and observers, and dispatch events.
7
9
  class Conductor
8
- attr_accessor :observers
9
-
10
10
  # Create with a set of observers.
11
11
  def initialize(observers)
12
- @observers = observers
12
+ @dispatcher = Event::Dispatcher.new observers
13
+ end
14
+
15
+ def observers
16
+ @dispatcher.observers
17
+ end
18
+
19
+ def observers=(*args)
20
+ @dispatcher.send :observers=, *args
13
21
  end
14
22
 
15
23
  # Start reading from the reader, which is an IO-like object.
16
24
  #
17
25
  # Parse each line and dispatch it as an event object to all observers.
18
26
  def start(reader)
19
- dispatch Event.new 'startup'
20
- parser = EventParser.new
21
- reader.each_line do |line|
22
- event = parser.parse line
23
- dispatch event
24
- end
25
- dispatch Event.new 'shutdown'
27
+ dispatch Event::BasicEvent.new 'startup'
28
+ parser = Event::Parser.new
29
+
30
+ while line = reader.gets
31
+ Torkify.logger.debug { "Read line: #{line}" }
32
+ events = parser.parse line
33
+ dispatch(*events)
34
+ end
35
+
36
+ dispatch Event::BasicEvent.new 'shutdown'
26
37
  end
27
38
 
28
39
  protected
29
- def dispatch(event)
30
- @observers.dispatch(event)
40
+ def dispatch(*events)
41
+ events.each { |event| @dispatcher.dispatch(event) }
31
42
  end
32
43
  end
33
44
  end
@@ -1,6 +1,6 @@
1
- require_relative 'event_message'
1
+ require_relative 'message'
2
2
 
3
- module Torkify
3
+ module Torkify::Event
4
4
 
5
5
  # Event used for all events that have no associated data.
6
6
  #
@@ -10,8 +10,8 @@ module Torkify
10
10
  # - shutdown
11
11
  # - startup
12
12
  # - anything else...
13
- class Event < Struct.new(:type)
14
- include EventMessage
13
+ class BasicEvent < Struct.new(:type)
14
+ include Message
15
15
 
16
16
  def to_s
17
17
  type
@@ -0,0 +1,91 @@
1
+ require_relative 'ran_all_test_files_event'
2
+
3
+ module Torkify::Event
4
+ class Dispatcher
5
+ attr_accessor :observers
6
+
7
+ def initialize(observers)
8
+ @observers = observers
9
+ end
10
+
11
+ def dispatch(event)
12
+ observers.each do |o|
13
+ ObserverEventDispatcher.new(o, event).dispatch
14
+ end
15
+ act_if_required event
16
+ end
17
+
18
+ protected
19
+
20
+ def act_if_required(event)
21
+ case event.type
22
+ when :run_all_test_files
23
+ @ran_all_tests_event = RanAllTestFilesEvent.new(:ran_all_test_files, [], [])
24
+ when :pass
25
+ if @ran_all_tests_event
26
+ @ran_all_tests_event.passed << event
27
+ end
28
+ when :fail
29
+ if @ran_all_tests_event
30
+ @ran_all_tests_event.failed << event
31
+ end
32
+ when :idle
33
+ if @ran_all_tests_event
34
+ @ran_all_tests_event.stop!
35
+ dispatch @ran_all_tests_event
36
+ @ran_all_tests_event = nil
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ class ObserverEventDispatcher
43
+ def initialize(observer, event)
44
+ @observer = observer
45
+ @event = event
46
+ end
47
+
48
+ def dispatch
49
+ dispatch_and_rescue
50
+ rescue => e
51
+ Torkify.logger.error { "Caught exception from observer #{observer.inspect} during ##{event.message}: #{e}\n\t#{e.backtrace.join("\n\t")}" }
52
+ end
53
+
54
+ protected
55
+ attr_reader :observer, :event
56
+
57
+ def dispatch_and_rescue
58
+ send_and_trap arguments_for_event_message
59
+ rescue NameError
60
+ send_args = arguments_by_arity 1
61
+ send_and_trap send_args
62
+ end
63
+
64
+ def send_and_trap(args)
65
+ observer.send event.message, *args
66
+ rescue NoMethodError => e
67
+ if e.message.include? event.message.to_s
68
+ Torkify.logger.debug { "No method #{event.message} defined on #{observer.inspect}" }
69
+ else
70
+ raise
71
+ end
72
+ end
73
+
74
+ def arguments_for_event_message
75
+ arguments_by_arity observer.method(event.message).arity
76
+ end
77
+
78
+ def arguments_by_arity(method_arity)
79
+ case method_arity
80
+ when 0
81
+ []
82
+ when -1, 1
83
+ [event]
84
+ else
85
+ [event].fill(nil, 1, method_arity - 1)
86
+ end
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'message'
2
+
3
+ module Torkify::Event
4
+
5
+ # Event used for all events that have no associated data.
6
+ #
7
+ # Types:
8
+ #
9
+ # - absorb
10
+ # - shutdown
11
+ # - startup
12
+ # - anything else...
13
+ class EchoEvent < Struct.new(:type, :arguments)
14
+ include Message
15
+
16
+ def to_s
17
+ type
18
+ end
19
+
20
+ alias :args :arguments
21
+ end
22
+ end
23
+
@@ -1,5 +1,5 @@
1
- module Torkify
2
- module EventMessage
1
+ module Torkify::Event
2
+ module Message
3
3
  def message
4
4
  "on_#{type}".to_sym
5
5
  end
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+ require_relative 'basic_event'
3
+ require_relative 'test_event'
4
+ require_relative 'pass_or_fail_event'
5
+ require_relative 'status_change_event'
6
+ require_relative 'echo_event'
7
+
8
+ module Torkify::Event
9
+
10
+ # Parse raw strings passed by tork into event objects.
11
+ class Parser
12
+
13
+ # Parse a raw string and return an object based on the event type.
14
+ #
15
+ # E.g. a raw string like:
16
+ # > '["test","spec/reader_spec.rb",[],"spec/reader_spec.rb.log",3]'
17
+ def parse(line)
18
+ raw = JSON.load line
19
+ event_from_data raw
20
+ end
21
+
22
+ protected
23
+ # Create an event object from the array of data.
24
+ def event_from_data(data)
25
+ case data.first
26
+ when 'test'
27
+ [ TestEvent.new(*data) ]
28
+ when 'pass', 'fail'
29
+ [ PassOrFailEvent.new(*data) ]
30
+ when 'pass_now_fail', 'fail_now_pass'
31
+ [ StatusChangeEvent.new(data[0], data[1], event_from_data(data[2]).first) ]
32
+ when 'echo'
33
+ parse_echo_event data
34
+ else
35
+ [ BasicEvent.new(data.first) ]
36
+ end
37
+ end
38
+
39
+ def parse_echo_event(data)
40
+ events = [ EchoEvent.new(*data) ]
41
+ event = event_from_echo(*data[1])
42
+ events << event if event
43
+ events
44
+ end
45
+
46
+ def event_from_echo(action, *action_args)
47
+ if echo_commands_requiring_args.include? action
48
+ return if action_args.empty? || action_args.first.empty?
49
+ end
50
+ if known_echo_commands.include? action
51
+ BasicEvent.new action
52
+ end
53
+ end
54
+
55
+ def known_echo_commands
56
+ [ 'run_all_test_files',
57
+ 'run_test_files',
58
+ 'run_test_file',
59
+ 'stop_running_test_files',
60
+ 'stop_running_test_files',
61
+ 'rerun_passed_test_files',
62
+ 'rerun_failed_test_files' ]
63
+ end
64
+
65
+ def echo_commands_requiring_args
66
+ [ 'run_test_files',
67
+ 'run_test_file' ]
68
+ end
69
+ end
70
+ end
@@ -1,6 +1,6 @@
1
- require_relative 'event_message'
1
+ require_relative 'message'
2
2
 
3
- module Torkify
3
+ module Torkify::Event
4
4
 
5
5
  # Event used for test passes or failures.
6
6
  #
@@ -9,7 +9,7 @@ module Torkify
9
9
  # - pass
10
10
  # - fail
11
11
  class PassOrFailEvent < Struct.new(:type, :file, :lines, :log_file, :worker, :exit_code, :exit_info)
12
- include EventMessage
12
+ include Message
13
13
 
14
14
  # Get the PID from the exit info.
15
15
  def pid
@@ -17,9 +17,21 @@ module Torkify
17
17
  matched.first.to_i if matched
18
18
  end
19
19
 
20
+ def errors
21
+ @errors ||= parse_errors_from_log
22
+ end
23
+
20
24
  def to_s
21
25
  s = "#{type.upcase} #{file}"
22
26
  s += lines.any? ? " (lines #{lines.join(', ')})" : ''
23
27
  end
28
+
29
+ protected
30
+ def parse_errors_from_log
31
+ log = File.open log_file
32
+ parser = Torkify::Log::Parser.new log
33
+ parser.parse
34
+ parser.errors
35
+ end
24
36
  end
25
37
  end
@@ -0,0 +1,30 @@
1
+ require_relative 'message'
2
+
3
+ module Torkify::Event
4
+
5
+ # Event used when all tests have finished running.
6
+ #
7
+ # The time method gives the approximate time in seconds that the tests
8
+ # took to run.
9
+ class RanAllTestFilesEvent < Struct.new(:type, :passed, :failed)
10
+ include Message
11
+
12
+ def initialize(type, passed = [], failed = [])
13
+ @created = Time.now.to_f
14
+ super
15
+ end
16
+
17
+ def stop!
18
+ if @time.nil?
19
+ @time = Time.now.to_f - @created
20
+ end
21
+ self
22
+ end
23
+
24
+ def time
25
+ stop!
26
+ @time
27
+ end
28
+ end
29
+ end
30
+
@@ -1,6 +1,6 @@
1
- require_relative 'event_message'
1
+ require_relative 'message'
2
2
 
3
- module Torkify
3
+ module Torkify::Event
4
4
 
5
5
  # Event used for changes in test status.
6
6
  #
@@ -11,7 +11,7 @@ module Torkify
11
11
  #
12
12
  # Includes the actual fail/pass event as a separate object.
13
13
  class StatusChangeEvent < Struct.new(:type, :file, :event)
14
- include EventMessage
14
+ include Message
15
15
 
16
16
  def to_s
17
17
  "#{type.upcase.gsub('_',' ')} #{file}"
@@ -1,11 +1,12 @@
1
- require_relative 'event_message'
1
+ require_relative 'message'
2
+
3
+ module Torkify::Event
2
4
 
3
- module Torkify
4
5
  # Event used when a test is started.
5
6
  #
6
7
  # This is currently only one type: 'test'
7
8
  class TestEvent < Struct.new(:type, :file, :lines, :log_file, :worker)
8
- include EventMessage
9
+ include Message
9
10
 
10
11
  def to_s
11
12
  s = "#{type.upcase} #{file}"
@@ -1,5 +1,5 @@
1
+ require 'set'
1
2
  require_relative 'conductor'
2
- require_relative 'observer_set'
3
3
  require_relative 'reader'
4
4
  require_relative 'exceptions'
5
5
 
@@ -12,7 +12,7 @@ module Torkify
12
12
  def initialize(command = 'tork-remote tork-engine', dir = Dir.pwd)
13
13
  @command = command
14
14
  @dir = dir
15
- @conductor = Conductor.new ObserverSet.new
15
+ @conductor = Conductor.new Set.new
16
16
  end
17
17
 
18
18
  # Add an observer object to be notified of tork events.
@@ -72,20 +72,29 @@ module Torkify
72
72
  # Start the torkify listener and tork itself.
73
73
  #
74
74
  # It forks the current process and runs torkify as the child. It then
75
- # runs tork as the main process, allowing for stdin to be passed to tork.
75
+ # runs the tork CLI as the main process, allowing for stdin to be passed to tork.
76
76
  #
77
77
  # Calls #start_loop().
78
- #
79
- # The command to run tork can be passed as an argument, and the
80
- # TORK_CONFIGS environment variable can be passed as the second argument.
81
- def start_with_tork(command = 'tork', tork_env = 'default')
78
+ def start_with_tork
79
+ load_tork
80
+
82
81
  if fork
83
82
  start_loop
84
83
  else
85
84
  # Run tork in main process to keep stdin
86
85
  Torkify.logger.info { "Starting tork" }
87
- exec({'TORK_CONFIGS' => tork_env}, command)
86
+
87
+ $0 = File.basename Dir.pwd
88
+ Tork::CLIApp.new.loop
88
89
  end
89
90
  end
91
+
92
+ def load_tork
93
+ require 'tork/config'
94
+ require 'tork/cliapp'
95
+ rescue LoadError
96
+ Torkify.logger.fatal { "Could not load tork: try running `gem install tork`" }
97
+ raise
98
+ end
90
99
  end
91
100
  end
@@ -0,0 +1,33 @@
1
+
2
+ module Torkify::Log
3
+ class LineMatcher
4
+ PATTERNS = {
5
+ 'tork_load_line' => /^Loaded suite tork[^\s]+\s(.+)/,
6
+ 'error_description' => /^[\s#]*([^:]+):([0-9]+):in/,
7
+ 'file_extraction' => /\[([^:]+):([0-9]+)\]/m,
8
+ 'tork_error_line' => /^.+tork\/master\.rb:[0-9]+:in [^:]+:\s/,
9
+ 'test_error_or_failure' => /^(\s+[0-9]+\)|Failure(?!s)|Error)/,
10
+ 'test_summary' => /^([0-9]+\s[a-z]+,)+/,
11
+ 'finished_line' => /^Finished/
12
+ }
13
+
14
+ def initialize(line)
15
+ self.line = line
16
+ end
17
+
18
+ PATTERNS.each do |name, reg|
19
+ define_method("#{name}?") { !(line =~ PATTERNS[name]).nil? }
20
+ define_method("#{name}") { PATTERNS[name].match(line) }
21
+ end
22
+
23
+ def end_of_errors?
24
+ test_summary? || finished_line?
25
+ end
26
+
27
+ alias :ruby_error :error_description
28
+ alias :ruby_error? :error_description?
29
+
30
+ protected
31
+ attr_accessor :line
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'line_matcher'
2
+
3
+ module Torkify::Log
4
+ class LogReader
5
+ attr_reader :line
6
+
7
+ def initialize(stream)
8
+ @stream = stream
9
+ @line = stream.readline
10
+ end
11
+
12
+ def forward
13
+ self.line = stream.readline
14
+ self
15
+ end
16
+
17
+
18
+ def matcher
19
+ @matcher ||= LineMatcher.new(line)
20
+ end
21
+
22
+ protected
23
+ attr_reader :stream
24
+ attr_writer :line
25
+
26
+ def line=(line)
27
+ @line = line
28
+ @matcher = nil
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,96 @@
1
+ require_relative 'test_error'
2
+ require_relative 'log_reader'
3
+
4
+ module Torkify::Log
5
+ module Error; end
6
+ class ParserError < StandardError; end
7
+
8
+ class Parser
9
+ attr_reader :errors
10
+
11
+ def initialize(stream)
12
+ @reader = LogReader.new stream
13
+ self.errors = []
14
+ end
15
+
16
+ def parse
17
+ parse_log
18
+ self
19
+ rescue EOFError
20
+ # noop
21
+ self
22
+ rescue Exception => e
23
+ # Tag all exceptions with Torkify::Error
24
+ e.extend Error
25
+ raise e
26
+ end
27
+
28
+ protected
29
+ attr_writer :errors
30
+ attr_reader :reader
31
+
32
+ def parse_log
33
+ loop do
34
+ if reader.matcher.ruby_error?
35
+ parse_ruby_error
36
+ if errors.empty?
37
+ raise ParserError, "Failed to read error from log file"
38
+ end
39
+ break
40
+ elsif tork_line_match = reader.matcher.tork_load_line
41
+ @file_fallback = tork_line_match[1].strip + ".rb"
42
+ elsif reader.matcher.test_error_or_failure?
43
+ parse_errors_or_failures
44
+ end
45
+ reader.forward
46
+ end
47
+ end
48
+
49
+ def parse_ruby_error
50
+ line = reader.line
51
+ if tork_match = reader.matcher.tork_error_line
52
+ line.slice! tork_match[0]
53
+ end
54
+ matches = line.split(':')
55
+ if matches.length >= 3
56
+ self.errors << TestError.new(matches.shift.strip,
57
+ matches.shift.strip,
58
+ matches.join(':').strip.gsub("'", "`"),
59
+ 'E')
60
+ end
61
+ end
62
+
63
+ def apply_file_from_matches(error, matches)
64
+ if matches
65
+ matches = matches.to_a
66
+ matches.shift
67
+ error.filename = matches.shift.strip
68
+ error.lnum = matches.shift.strip
69
+ end
70
+ end
71
+
72
+ def parse_errors_or_failures
73
+ until reader.matcher.end_of_errors?
74
+ if reader.matcher.test_error_or_failure?
75
+ error = TestError.new(@file_fallback, '0', '', 'E')
76
+ self.errors << error
77
+ end
78
+
79
+ matches = reader.matcher.error_description
80
+ error.text << reader.line.gsub("'", "`")
81
+
82
+ apply_file_from_matches error, matches
83
+ reader.forward
84
+ end
85
+
86
+ errors.each do |err|
87
+ if err.lnum == '0'
88
+ matches = err.text.match(LineMatcher::PATTERNS['file_extraction'])
89
+ apply_file_from_matches err, matches
90
+ end
91
+ end
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,7 @@
1
+ module Torkify::Log
2
+ class TestError < Struct.new(:filename, :lnum, :text, :type)
3
+ def clean_text
4
+ text.strip
5
+ end
6
+ end
7
+ end
@@ -12,10 +12,10 @@ module Torkify
12
12
  # has been written to the command's STDERR stream.
13
13
  def initialize(command = 'tork-remote tork-engine', run_in_dir = Dir.pwd)
14
14
  Dir.chdir(run_in_dir) do
15
- _, @io, stderr, _ = Open3.popen3 command
15
+ self.in, self.out, self.err, self.thread = Open3.popen3 command
16
16
 
17
- if @io.eof?
18
- raise TorkError, stderr.read.strip
17
+ if out.eof?
18
+ raise TorkError, err.read.strip
19
19
  end
20
20
  end
21
21
  end
@@ -24,12 +24,14 @@ module Torkify
24
24
  #
25
25
  # This allows this class to be used in an IO like way.
26
26
  def method_missing(method, *args, &blk)
27
- @io.send method, *args, &blk
27
+ out.send method, *args, &blk
28
28
  end
29
29
 
30
30
  # Allow respond_to? to work with method_missing.
31
31
  def respond_to?(method, include_private = false)
32
- @io.respond_to? method, include_private
32
+ out.respond_to? method, include_private
33
33
  end
34
+ protected
35
+ attr_accessor :in, :out, :err, :thread
34
36
  end
35
37
  end
@@ -1,3 +1,3 @@
1
1
  module Torkify
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end