otaku 0.2.2 → 0.3.0
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/HISTORY.txt +8 -0
- data/README.rdoc +18 -5
- data/VERSION +1 -1
- data/examples/unittest/client.rb +17 -0
- data/examples/unittest/server.rb +56 -0
- data/examples/unittest/server2.rb +11 -0
- data/examples/unittest/tests/a_test.rb +9 -0
- data/examples/unittest/tests/b_test.rb +9 -0
- data/lib/otaku.rb +1 -0
- data/lib/otaku/handler.rb +16 -108
- data/lib/otaku/handler/context.rb +43 -0
- data/lib/otaku/handler/magic_proc.rb +116 -0
- data/lib/otaku/handler/processor.rb +78 -0
- data/otaku.gemspec +11 -3
- data/spec/handler_spec.rb +905 -23
- data/spec/integration_spec.rb +6 -1
- metadata +17 -4
data/HISTORY.txt
CHANGED
data/README.rdoc
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
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.
|
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
|
data/lib/otaku.rb
CHANGED
data/lib/otaku/handler.rb
CHANGED
@@ -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 =
|
6
|
-
|
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
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|