otaku 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ === 0.3.0 2010-08-05
2
+
3
+ = Features
4
+ * Proc can now be passed as a context to Otaku.start [#ngty]
5
+
6
+ = Maintenance
7
+ * Cleaning up Otaku::Handler [#ngty]
8
+
1
9
  === 0.2.2 2010-07-21
2
10
 
3
11
  = Features
@@ -4,7 +4,11 @@ Dead simple server/client service built using eventmachine.
4
4
 
5
5
  == Introduction
6
6
 
7
- Otaku's original intent is to support testing of cross-process stubbing in cross-stub (http://github.com/ngty/cross-stub). It's usefulness in other aspects of my hacking life prompts me to extract it out, & package it as a generic solution. Its primary intent is to be dead simple to use & easy to customize, of course, both criteria subjected to very my own tastes.
7
+ Otaku's original intent is to support testing of cross-process stubbing in
8
+ cross-stub (http://github.com/ngty/cross-stub). It's usefulness in other
9
+ aspects of my hacking life prompts me to extract it out, & package it as a
10
+ generic solution. Its primary intent is to be dead simple to use & easy to
11
+ customize, of course, both criteria subjected to very my own tastes.
8
12
 
9
13
  == Getting Started
10
14
 
@@ -31,7 +35,8 @@ It's hosted on rubygems.org:
31
35
 
32
36
  === Unfortunately ...
33
37
 
34
- Most of the times, we won't have anything as simple as above, the following illustrates the problem of contextual reference:
38
+ Most of the times, we won't have anything as simple as above, the following
39
+ illustrates the problem of contextual reference:
35
40
 
36
41
  mark = '*'
37
42
  Otaku.start do |data|
@@ -40,7 +45,9 @@ Most of the times, we won't have anything as simple as above, the following illu
40
45
 
41
46
  Otaku.process('hello') # failure !!
42
47
 
43
- The reason is that the proc that we passed to Otaku.start is being marshalled while being passed to the server as a handler, in the process, the contextual references are lost. A workaround for this problem is:
48
+ The reason is that the proc that we passed to Otaku.start is being marshalled
49
+ while being passed to the server as a handler, in the process, the contextual
50
+ references are lost. A workaround for this problem is:
44
51
 
45
52
  Otaku.start(:mark => '*') do |data|
46
53
  '%s %s %s' % [mark, data, mark]
@@ -48,6 +55,11 @@ The reason is that the proc that we passed to Otaku.start is being marshalled wh
48
55
 
49
56
  Otaku.process('hello') # >> '* hello *'
50
57
 
58
+ Anything that can be marshalled can be passed as a context. The exception is
59
+ proc, which cannot be marshalled, yet it can still be passed in. However, in
60
+ the process, all contextual references within the proc are lost, so u may
61
+ want to exercise care when playing with proc.
62
+
51
63
  == Configuraing It
52
64
 
53
65
  Otaku ships with the following defaults:
@@ -88,8 +100,9 @@ Configuring can be done via:
88
100
  * Make your feature addition or bug fix.
89
101
  * Add tests for it. This is important so I don't break it in a
90
102
  future version unintentionally.
91
- * Commit, do not mess with rakefile, version, or history.
92
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
103
+ * Commit, do not mess with rakefile, version, or history. (if you want to have
104
+ your own version, that is fine but bump version in a commit by itself I can
105
+ ignore when I pull)
93
106
  * Send me a pull request. Bonus points for topic branches.
94
107
 
95
108
  == Copyright
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.3.0
@@ -0,0 +1,17 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'otaku')
2
+
3
+ Otaku.port = 10901
4
+
5
+ Otaku.start do |test_file|
6
+ unless $o_stdout
7
+ $o_stdout, $o_stderr = $stdout, $stderr
8
+ $stdout, $stderr = StringIO.new, StringIO.new
9
+ end
10
+ MiniTest::Unit::TestCase.reset
11
+ require File.join(File.dirname(__FILE__), test_file)
12
+ MiniTest::Unit.new.run
13
+ $stdout.rewind
14
+ $stdout.lines
15
+ end
16
+
17
+
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'otaku')
4
+
5
+ Otaku.port = 10901
6
+
7
+ Otaku.start do |test_file|
8
+
9
+ unless $o_stdout
10
+ $o_stdout, $o_stderr = $stdout, $stderr
11
+ $stdout, $stderr = StringIO.new, StringIO.new
12
+ end
13
+
14
+ require 'rubygems'
15
+ require 'minitest/unit'
16
+ MiniTest::Unit::TestCase.reset
17
+ require test_file
18
+ MiniTest::Unit.new.run
19
+
20
+ results = ($stdout.rewind; $stdout).lines.inject({}) do |memo, line|
21
+ case line
22
+ when /(\d+) assertions, (\d+) failures, (\d+) errors, (\d+) skips/
23
+ a, f, e, s = [$1, $2, $3, $4].map(&:to_i)
24
+ memo[:stats] = {:assertions => a, :failures => f, :errors => e, :skips => s})
25
+ when /Finished in ([\d\.]+) seconds/
26
+ memo[:time_taken] = "#{$1} seconds"
27
+ else
28
+ (memo[:buffer] ||= []) << line
29
+ end
30
+ memo
31
+ end
32
+
33
+ unless [:errors, :failures].all?{|type| results[:stats][type].zero? }
34
+ results[:errors] = (results.delete(:buffer) || []).inject([]) do |errors, line|
35
+ latest = errors[-1]
36
+ if title = line[/^ (\d+\) (Error|Failure)\:)/,1]
37
+ errors << [title]
38
+ elsif latest && [1,2].include?(latest.size)
39
+ latest << line.strip
40
+ elsif latest && latest.size == 3 && line.include?(test_file)
41
+ latest << [test_file, line[/\:(\d+)\:/,1]].join(':')
42
+ end
43
+ errors
44
+ end
45
+ end
46
+
47
+ results
48
+ end
49
+
50
+ Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'tests', '*.rb')) do |file|
51
+ pp Otaku.process(file)
52
+ end
53
+
54
+ at_exit do
55
+ Otaku.stop
56
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+
4
+ Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'tests', '*.rb')) do |file|
5
+ require 'rubygems'
6
+ require 'minitest/unit'
7
+ MiniTest::Unit::TestCase.reset
8
+ require file
9
+ MiniTest::Unit.new.run
10
+ end
11
+
@@ -0,0 +1,9 @@
1
+ require 'minitest/unit'
2
+
3
+ class TestA < MiniTest::Unit::TestCase
4
+
5
+ def test_a
6
+ assert true
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'minitest/unit'
2
+
3
+ class TestB < MiniTest::Unit::TestCase
4
+
5
+ def test_b
6
+ assert true
7
+ end
8
+
9
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'forwardable'
2
3
  require 'eventmachine'
3
4
  require 'logger'
4
5
  require 'base64'
@@ -1,124 +1,32 @@
1
+ require 'otaku/handler/magic_proc'
2
+ require 'otaku/handler/context'
3
+ require 'otaku/handler/processor'
4
+
1
5
  module Otaku
2
6
  class Handler
3
7
 
8
+ attr_reader :context, :processor
9
+
4
10
  def initialize(context, handler)
5
- @context = __context_as_code__(context)
6
- magic_proc = MagicProc.new(handler)
7
- @proc, @file, @line = [:code, :file, :line].map{|meth| magic_proc.send(meth) }
11
+ @context = Context.new(context)
12
+ @processor = Processor.new(handler)
8
13
  end
9
14
 
10
15
  def process(data)
11
- (instance = eval(@context, nil, '(generated class)', 1)).
12
- instance_exec(data, &(block = eval(@proc, nil, @file, @line)))
16
+ @context.eval!.instance_exec(data, &@processor.eval!)
13
17
  end
14
18
 
15
19
  def root
16
- File.dirname(@file)
20
+ File.dirname(@processor.file)
17
21
  end
18
22
 
19
- private
20
-
21
- def __context_as_code__(methods_hash)
22
- 'Class.new{ %s }.new' %
23
- methods_hash.map do |method, val|
24
- "def #{method}; Encoder.decode(%|#{Encoder.encode(val).gsub('|','\|')}|); end"
25
- end.sort.join('; ')
26
- end
27
-
28
- class MagicProc
29
-
30
- RUBY_PARSER = RubyParser.new
31
- RUBY_2_RUBY = Ruby2Ruby.new
32
-
33
- attr_reader :file, :line, :code
34
-
35
- def initialize(block)
36
- @block = block
37
- extract_file_and_line_and_code
38
- end
39
-
40
- private
41
-
42
- def extract_file_and_line_and_code
43
- code, remaining = code_fragments
44
-
45
- while frag = remaining[frag_regexp,1]
46
- begin
47
- sexp = RUBY_PARSER.parse(replace_magic_vars(code += frag))
48
- if sexp.inspect =~ sexp_regexp
49
- @code = revert_magic_vars(RUBY_2_RUBY.process(sexp)).sub('proc {','lambda {')
50
- break
51
- end
52
- rescue SyntaxError, Racc::ParseError, NoMethodError
53
- remaining.sub!(frag,'')
54
- end
55
- end
56
- end
57
-
58
- def code_fragments
59
- ignore, start_marker, arg =
60
- [:ignore, :start_marker, :arg].map{|key| code_match_args[key] }
61
- [
62
- "proc #{start_marker} |#{arg}| ",
63
- source_code.sub(ignore, '')
64
- ]
65
- end
66
-
67
- def sexp_regexp
68
- @sexp_regexp ||= (
69
- Regexp.new([
70
- Regexp.quote("s(:iter, s(:call, nil, :"),
71
- "(proc|lambda)",
72
- Regexp.quote(", s(:arglist)), s(:lasgn, :#{code_match_args[:arg]}), s("),
73
- ].join)
74
- )
75
- end
76
-
77
- def frag_regexp
78
- @frag_regexp ||= (
79
- end_marker = {'do' => 'end', '{' => '\}'}[code_match_args[:start_marker]]
80
- /^(.*?\W#{end_marker})/m
81
- )
82
- end
83
-
84
- def code_regexp
85
- @code_regexp ||=
86
- /^(.*?(Otaku\.start.*?|lambda|proc|Proc\.new)\s*(do|\{)\s*\|(\w+)\|\s*)/m
87
- end
88
-
89
- def code_match_args
90
- @code_match_args ||= (
91
- args = source_code.match(code_regexp)
92
- {
93
- :ignore => args[1],
94
- :start_marker => args[3],
95
- :arg => args[4]
96
- }
97
- )
98
- end
99
-
100
- def source_code
101
- @source_code ||= (
102
- file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(@block.inspect)[1..2]
103
- @file = File.expand_path(file)
104
- @line = line.to_i
105
- File.readlines(@file)[@line.pred .. -1].join
106
- )
107
- end
108
-
109
- def replace_magic_vars(code)
110
- %w{__FILE__ __LINE__}.inject(code) do |code, var|
111
- code.gsub(var, "%|((#{var}))|")
112
- end
113
- end
114
-
115
- def revert_magic_vars(code)
116
- %w{__FILE__ __LINE__}.inject(code) do |code, var|
117
- code.gsub(%|"((#{var}))"|, var)
118
- end
119
- end
23
+ def marshal_dump
24
+ [@context, @processor]
25
+ end
120
26
 
121
- end
27
+ def marshal_load(data)
28
+ @context, @processor = data
29
+ end
122
30
 
123
31
  end
124
32
  end
@@ -0,0 +1,43 @@
1
+ module Otaku
2
+ class Handler
3
+ class Context #:nodoc:
4
+
5
+ attr_reader :code
6
+
7
+ def initialize(methods_hash)
8
+ @code = 'Class.new{ %s }.new' %
9
+ methods_hash.map do |method, val|
10
+ 'def %s; %s; end' % [method, method_body(val)]
11
+ end.sort.join('; ')
12
+ end
13
+
14
+ def eval!
15
+ eval(@code, nil, '(generated class)', 1)
16
+ end
17
+
18
+ def marshal_dump
19
+ [@code]
20
+ end
21
+
22
+ def marshal_load(data)
23
+ @code, _ = data
24
+ end
25
+
26
+ private
27
+
28
+ def method_body(val)
29
+ if val.is_a?(Proc)
30
+ "Encoder.decode(%|#{Encoder.encode(magic_proc(val)).gsub('|','\|')}|).eval!"
31
+ else
32
+ "Encoder.decode(%|#{Encoder.encode(val).gsub('|','\|')}|)"
33
+ end
34
+ end
35
+
36
+ def magic_proc(block)
37
+ MagicProc.new(block)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,116 @@
1
+ module Otaku
2
+ class Handler
3
+ class MagicProc #:nodoc:
4
+
5
+ RUBY_PARSER = RubyParser.new
6
+ RUBY_2_RUBY = Ruby2Ruby.new
7
+
8
+ attr_reader :file, :line, :code
9
+
10
+ def initialize(block)
11
+ @cache = {:block => block}
12
+ extract_file_and_line_and_code
13
+ @cache = nil
14
+ end
15
+
16
+ def eval!
17
+ eval(@code, nil, @file, @line)
18
+ end
19
+
20
+ def marshal_dump
21
+ [@code, @file, @line]
22
+ end
23
+
24
+ def marshal_load(data)
25
+ @code, @file, @line = data
26
+ end
27
+
28
+ private
29
+
30
+ def extract_file_and_line_and_code
31
+ code, remaining = code_fragments
32
+
33
+ while frag = remaining[frag_regexp,1]
34
+ begin
35
+ sexp = RUBY_PARSER.parse(replace_magic_vars(code += frag))
36
+ if sexp.inspect =~ sexp_regexp
37
+ @code = revert_magic_vars(RUBY_2_RUBY.process(sexp)).sub('proc {','lambda {')
38
+ break
39
+ end
40
+ rescue SyntaxError, Racc::ParseError, NoMethodError
41
+ remaining.sub!(frag,'')
42
+ end
43
+ end
44
+ end
45
+
46
+ def code_fragments
47
+ ignore, start_marker, arg =
48
+ [:ignore, :start_marker, :arg].map{|key| code_match_args[key] }
49
+ [
50
+ arg ? "proc #{start_marker} |#{arg}|" : "proc #{start_marker}",
51
+ source_code.sub(ignore, '')
52
+ ]
53
+ end
54
+
55
+ def sexp_regexp
56
+ @cache[:sexp_regexp] ||= (
57
+ Regexp.new([
58
+ Regexp.quote("s(:iter, s(:call, nil, :"),
59
+ "(proc|lambda)",
60
+ Regexp.quote(", s(:arglist)), "),
61
+ '(%s|%s|%s)' % [
62
+ Regexp.quote('s(:masgn, s(:array, s('),
63
+ Regexp.quote('s(:lasgn, :'),
64
+ Regexp.quote('nil, s(')
65
+ ]
66
+ ].join)
67
+ )
68
+ end
69
+
70
+ def frag_regexp
71
+ @cache[:frag_regexp] ||= (
72
+ end_marker = {'do' => 'end', '{' => '\}'}[code_match_args[:start_marker]]
73
+ /^(.*?\W#{end_marker})/m
74
+ )
75
+ end
76
+
77
+ def code_regexp
78
+ @cache[:code_regexp] ||=
79
+ /^(.*?(lambda|proc|Proc\.new)?\s*(do|\{)\s*(\|(.*?)\|\s*)?)/m
80
+ end
81
+
82
+ def code_match_args
83
+ @cache[:code_match_args] ||= (
84
+ args = source_code.match(code_regexp)
85
+ {
86
+ :ignore => args[1],
87
+ :start_marker => args[3],
88
+ :arg => args[5]
89
+ }
90
+ )
91
+ end
92
+
93
+ def source_code
94
+ @cache[:source_code] ||= (
95
+ file, line = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+).*?>$/.match(@cache[:block].inspect)[1..2]
96
+ @file = File.expand_path(file)
97
+ @line = line.to_i
98
+ File.readlines(@file)[@line.pred .. -1].join
99
+ )
100
+ end
101
+
102
+ def replace_magic_vars(code)
103
+ %w{__FILE__ __LINE__}.inject(code) do |code, var|
104
+ code.gsub(var, "%|((#{var}))|")
105
+ end
106
+ end
107
+
108
+ def revert_magic_vars(code)
109
+ %w{__FILE__ __LINE__}.inject(code) do |code, var|
110
+ code.gsub(%|"((#{var}))"|, var)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end