trip 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d170e305895c49de5afa27d65e21481a42b0647
4
+ data.tar.gz: d2245ff1028cbe489f101e53ac352c460db424d2
5
+ SHA512:
6
+ metadata.gz: 28639d5f905547c812c2696d6451984f2624b7327180e597b15f9891f972693204a179914fd6731caf346a97549a9f9088c0d76f79e4fad11e3722377406fe0c
7
+ data.tar.gz: c4ae55b130f2bf502a7c3626178065ab97128e44da94a9d16685b410451af9501938236a0500dc1dc3e352d1173e1b2ef0acf07255671c0bbdaed8cfa4a45acf
data/.bundle/config ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ BUNDLE_PATH: ".gems"
3
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .gems/
2
+ *.md~
3
+ *.rb~
4
+ *.rb#
5
+ pkg/
6
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ install: bundle install
2
+ script: ruby -S rake
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.1.3
7
+ - ruby-head
8
+
9
+ notifications:
10
+ email: true
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ group :test do
4
+ gem 'minitest', require: ['minitest', 'minitest/spec']
5
+ end
6
+
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 rpag
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ __trip__
2
+
3
+ [![Build Status](https://travis-ci.org/rpag/trip.svg?branch=master)](https://travis-ci.org/rpag/trip)
4
+ [![Code Climate](https://codeclimate.com/github/rpag/trip/badges/gpa.svg)](https://codeclimate.com/github/rpag/trip)
5
+
6
+ trip is a concurrent tracer for the ruby programming language. It yields control
7
+ between two threads, most likely the main thread and a thread that trip creates.
8
+ It is implemented in pure ruby(no C) and with the help of "Thread#set_trace_func".
9
+ It can be useful when building developer tools like a debugger or if you want to
10
+ step through code progmatically at runtime.
11
+
12
+
13
+ __Limitations__
14
+
15
+ trip is implemented with "Thread#set_trace_func". It can be a bottleneck,
16
+ especially when there are a large number of events being created. I have seen
17
+ ruby grind to a halt when the codepath being traced is busy. It wouldn't be a good
18
+ library to use with something like a production rails application, but it can be
19
+ useful when building developer tools.
20
+
21
+ "Thread#set_trace_func" isn't well supported on any ruby implementation but the
22
+ official MRI/CRuby implementations, so jruby+rubinius aren't supported.
23
+
24
+ __Examples__
25
+
26
+ __1.__
27
+
28
+ The code being traced is paused on method call and method return events
29
+ by default.
30
+
31
+ ```ruby
32
+ def add(x,y)
33
+ x + y
34
+ end
35
+
36
+ trip = Trip.new { add(20,50) }
37
+ e1 = trip.start # Trip::Event (for the method call of "#add")
38
+ e2 = trip.resume # Trip::Event (for the method return of "#add")
39
+ e3 = trip.resume # returns nil (thread exits)
40
+ ```
41
+
42
+ __2.__
43
+
44
+ A predicate Proc can be implemented as a custom value. It receives
45
+ an instance of `Trip::Event` that can support the Proc when it is
46
+ making a decision on whether or not it should pause the tracer by
47
+ returning false, or to continue by returning true.
48
+
49
+ ```ruby
50
+ class Planet
51
+ def initialize(name)
52
+ @name = name
53
+ end
54
+
55
+ def echo
56
+ 'ping'
57
+ end
58
+ end
59
+
60
+ trip = Trip.new { Planet.new('earth').echo }
61
+ trip.pause? { |e| # pause on a method call
62
+ e.name == 'call'
63
+ }
64
+ e1 = trip.start # Trip::Event (for the method call of `Planet#initialize`)
65
+ e2 = trip.resume # Trip::Event (for the method call of `Planet#echo`)
66
+ e3 = trip.resume # returns nil (thread exits)
67
+ ```
68
+
69
+ __3.__
70
+
71
+ Trip::Event#binding can be used to evaluate ruby while the trip
72
+ thread is sleeping. The example returns the value of the local
73
+ variable "x" after the add method has been called.
74
+
75
+ ```ruby
76
+ def add(x,y)
77
+ x + y
78
+ end
79
+
80
+ trip = Trip.new { add(2,3) }
81
+ e1 = trip.start # Trip::Event (for the method call of add)
82
+ x = e1.binding.eval('x') # returns 2 (x is equal to 2)
83
+ trip.stop # thread exits
84
+
85
+ puts "x is equal to #{x}"
86
+ ```
87
+
88
+ __Install__
89
+
90
+ rubygems:
91
+
92
+ gem install trip
93
+
94
+ git:
95
+
96
+ git clone https://github.com/rpag/trip.git
97
+ cd trip
98
+ gem build trip.gemspec
99
+ gem install *.gem
100
+
101
+ __License__
102
+
103
+ See MIT-LICENSE.txt file.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.test_files = FileList['spec/*_spec.rb']
6
+ t.verbose = false
7
+ end
8
+ task :default => :spec
@@ -0,0 +1,41 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'trip'
3
+
4
+ #
5
+ # an example method.
6
+ # it assigns a local variable and returns control to the caller.
7
+ #
8
+ def example
9
+ x = 1
10
+ end
11
+
12
+ #
13
+ # initialize Trip with a block that calls the example() method.
14
+ # the trace doesn't start yet.
15
+ #
16
+ trip = Trip.new do
17
+ example()
18
+ end
19
+
20
+ #
21
+ # the tracer can be paused on certain events.
22
+ #
23
+ trip.pause? do |event|
24
+ event.name == 'call' or event.name == 'return'
25
+ end
26
+
27
+ #
28
+ # start trip.
29
+ # e1 and e2 reference Trip::Event objects. e3 ends up referencing nil.
30
+ # nil is returned when trip has finished.
31
+ #
32
+ e1 = trip.start # before assignment of 'x'
33
+ e2 = trip.resume # after assignment of 'x'
34
+ e3 = trip.resume # finished
35
+
36
+ #
37
+ # print the value of 'x'.
38
+ # the local variable 'x' belongs to the binding closed over by e2(method return).
39
+ #
40
+ print 'x is equal to "%s".' % e2.binding.eval('x')
41
+
data/examples/time.rb ADDED
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'trip'
3
+
4
+ class Phone
5
+ def self.call
6
+ sleep(2)
7
+ 'ring ring'
8
+ end
9
+ end
10
+
11
+ trip = Trip.new { Phone.call }
12
+ trip.pause? { |e| e.name == 'call' or e.name == 'return' }
13
+ t1 = trip.start.created_at
14
+ t2 = trip.resume.created_at
15
+ puts "it took around #{t2 - t1} seconds"
data/lib/trip.rb ADDED
@@ -0,0 +1,167 @@
1
+ class Trip
2
+ require 'thread'
3
+ require_relative 'trip/event'
4
+ require_relative 'trip/version'
5
+
6
+ NotStartedError = Class.new(RuntimeError)
7
+ NotFinishedError = Class.new(RuntimeError)
8
+
9
+ RUN_STATE = 'run'
10
+ SLEEP_STATE = 'sleep'
11
+ END_STATE = [nil, false]
12
+ CALL_E = ['call', 'c-call']
13
+ RETURN_E = ['return', 'c-return']
14
+ PAUSE_P = Proc.new { |event|
15
+ CALL_E.include?(event.name) or RETURN_E.include?(event.name)
16
+ }
17
+
18
+ #
19
+ # @param [Proc] &block
20
+ # a block of code
21
+ #
22
+ # @return [Trip]
23
+ # returns an instance of Trip
24
+ #
25
+ def initialize(&block)
26
+ if block.equal?(nil)
27
+ raise ArgumentError, 'expected to receive a block but none given'
28
+ end
29
+ @thread = nil
30
+ @block = block
31
+ @queue = nil
32
+ @pause = PAUSE_P
33
+ end
34
+
35
+ #
36
+ # @raise [ArgumentError]
37
+ # when no arguments are received
38
+ #
39
+ # @param [Proc] callable
40
+ # accepts a Proc or a block
41
+ #
42
+ # @return [Proc]
43
+ # returns a Proc
44
+ #
45
+ def pause?(callable = nil, &block)
46
+ pause = callable || block
47
+ if pause.equal?(nil)
48
+ raise ArgumentError, 'expected to receive a block but none given'
49
+ end
50
+ @pause = pause
51
+ end
52
+
53
+ #
54
+ # @return [Boolean]
55
+ # returns true when a trace has started
56
+ #
57
+ def started?
58
+ @thread != nil
59
+ end
60
+
61
+ #
62
+ # @return [Boolean]
63
+ # returns true when a tracer thread is running
64
+ #
65
+ def running?
66
+ @thread and @thread.status == RUN_STATE
67
+ end
68
+
69
+ #
70
+ # @return [Boolean]
71
+ # returns true when a tracer thread has finished
72
+ #
73
+ def finished?
74
+ @thread and END_STATE.include?(@thread.status)
75
+ end
76
+
77
+ #
78
+ # @return [Boolean]
79
+ # returns true when a tracer thread is sleeping
80
+ #
81
+ def sleeping?
82
+ @thread and @thread.status == SLEEP_STATE
83
+ end
84
+
85
+ #
86
+ # resume the tracer
87
+ #
88
+ # @raise [Trip::NotStartedError]
89
+ # when the start method has not been called yet
90
+ #
91
+ # @return [Trip::Event, nil]
92
+ # returns an event or nil
93
+ #
94
+ def resume
95
+ unless started?
96
+ raise NotStartedError, 'the tracer has not been started'
97
+ end
98
+ if sleeping?
99
+ @thread.wakeup
100
+ @queue.deq
101
+ end
102
+ end
103
+
104
+ #
105
+ # start the tracer
106
+ #
107
+ # @raise [Trip::NotFinishedError]
108
+ # when a trace is already in progress
109
+ #
110
+ # @return [Trip::Event, nil]
111
+ # returns an event, or nil
112
+ #
113
+ def start
114
+ if started? and !finished?
115
+ raise NotFinishedError, 'a trace is still in progress'
116
+ end
117
+ @queue = Queue.new
118
+ @thread = Thread.new do
119
+ Thread.current.set_trace_func method(:on_event).to_proc
120
+ @block.call
121
+ Thread.current.set_trace_func(nil)
122
+ @queue.enq(nil)
123
+ end
124
+ @queue.deq
125
+ end
126
+
127
+ #
128
+ # stop the tracer
129
+ #
130
+ # @return [nil]
131
+ # returns nil
132
+ #
133
+ def stop
134
+ if @thread
135
+ @thread.set_trace_func(nil)
136
+ @thread.exit
137
+ @thread.join
138
+ nil
139
+ end
140
+ end
141
+
142
+ private
143
+ def on_event(name, file, lineno, method, binding, classname)
144
+ event = Event.new name, {
145
+ file: file,
146
+ lineno: lineno,
147
+ module: classname,
148
+ method: method,
149
+ binding: binding
150
+ }
151
+ if event.file != __FILE__ and @pause.call(event)
152
+ @queue.enq(event)
153
+ Thread.stop
154
+ end
155
+ rescue Exception => e
156
+ warn <<-CRASH.each_line.map(&:lstrip)
157
+ (trip) the tracer has crashed! :(
158
+
159
+ #{e.class}:
160
+ #{e.message}
161
+
162
+ BACKTRACE
163
+ #{e.backtrace.join("\n")}
164
+ CRASH
165
+ Thread.current.set_trace_func(nil)
166
+ end
167
+ end
data/lib/trip/event.rb ADDED
@@ -0,0 +1,31 @@
1
+ class Trip::Event < BasicObject
2
+ Kernel = ::Kernel
3
+ Time = ::Time
4
+ CALLS = %w(call c-call)
5
+ RETURNS = %w(return c-return)
6
+
7
+ attr_reader :name, :created_at
8
+
9
+ def initialize(name, event)
10
+ @name = name
11
+ @event = event
12
+ @created_at = Time.now
13
+ end
14
+
15
+ [:file, :lineno, :module, :method, :binding].each do |name|
16
+ define_method(name) { @event[name] }
17
+ end
18
+
19
+ #
20
+ # @return [Binding]
21
+ # returns a binding for an instance of {Trip::Event}
22
+ #
23
+ def __binding__
24
+ Kernel.binding
25
+ end
26
+
27
+ def inspect
28
+ inspect_m = Kernel.instance_method(:inspect).bind(self)
29
+ inspect_m.call
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ class Trip
2
+ VERSION = '0.1.0'
3
+ end
data/spec/setup.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+ Bundler.require :default, :test
3
+ Minitest.autorun
data/spec/trip_spec.rb ADDED
@@ -0,0 +1,157 @@
1
+ require_relative 'setup'
2
+ describe Trip do
3
+ class Planet
4
+ def echo(message)
5
+ return message
6
+ end
7
+ end
8
+
9
+ before do
10
+ @planet = Planet.new
11
+ @trip = Trip.new { @planet.echo('ping') }
12
+ end
13
+
14
+ after do
15
+ unless @trip.finished?
16
+ @trip.stop
17
+ end
18
+ end
19
+
20
+ describe '#initialize' do
21
+ it 'raises an ArgumentError without a block' do
22
+ assert_raises(ArgumentError) { Trip.new }
23
+ end
24
+ end
25
+
26
+ describe '#start' do
27
+ it 'returns an instance of Trip::Event' do
28
+ assert Trip::Event === @trip.start
29
+ end
30
+
31
+ it 'returns nil with a false pause predicate' do
32
+ @trip.pause? { false }
33
+ assert_equal nil, @trip.start
34
+ end
35
+
36
+ it 'raises Trip::NotFinishedError' do
37
+ @trip.start
38
+ assert_raises(Trip::NotFinishedError) { @trip.start }
39
+ end
40
+ end
41
+
42
+ describe '#sleeping?' do
43
+ it 'returns true' do
44
+ @trip.start
45
+ assert_equal true, @trip.sleeping?
46
+ end
47
+
48
+ it 'returns false' do
49
+ @trip.start
50
+ @trip.resume while @trip.resume
51
+ assert_equal false, @trip.sleeping?
52
+ end
53
+ end
54
+
55
+ describe '#started?' do
56
+ it 'returns true' do
57
+ @trip.start
58
+ assert_equal true, @trip.started?
59
+ end
60
+
61
+ it 'returns false' do
62
+ assert_equal false, @trip.started?
63
+ end
64
+ end
65
+
66
+ describe '#resume' do
67
+ it 'raises Trip::NotStartedError' do
68
+ assert_raises(Trip::NotStartedError) { @trip.resume }
69
+ end
70
+ end
71
+
72
+ describe '#pause?' do
73
+ it 'raises an ArgumentError' do
74
+ assert_raises(ArgumentError) { @trip.pause? }
75
+ end
76
+
77
+ it 'accepts a Proc' do
78
+ obj = Proc.new {}
79
+ assert_equal obj, @trip.pause?(obj)
80
+ end
81
+ end
82
+
83
+ describe '#finished?' do
84
+ it 'returns true' do
85
+ @trip.start
86
+ @trip.resume while @trip.resume
87
+ assert_equal true, @trip.finished?
88
+ end
89
+
90
+ it 'returns false' do
91
+ @trip.start
92
+ assert_equal false, @trip.finished?
93
+ end
94
+
95
+ it 'returns nil' do
96
+ assert_equal nil, @trip.finished?
97
+ end
98
+ end
99
+
100
+ describe '#running?' do
101
+ it 'returns false' do
102
+ @trip.start
103
+ @trip.resume while @trip.resume
104
+ assert_equal false, @trip.running?
105
+ end
106
+
107
+ it 'returns nil' do
108
+ assert_equal nil, @trip.running?
109
+ end
110
+ end
111
+
112
+ describe Trip::Event do
113
+ describe '#module' do
114
+ it 'returns a Module' do
115
+ e1 = @trip.start
116
+ assert_equal Planet, e1.module
117
+ end
118
+ end
119
+
120
+ describe '#method' do
121
+ it 'returns a method name' do
122
+ e1 = @trip.start
123
+ assert_equal :echo, e1.method
124
+ end
125
+ end
126
+
127
+ describe '#file' do
128
+ it 'returns a path' do
129
+ e1 = @trip.start
130
+ assert_equal __FILE__, e1.file
131
+ end
132
+ end
133
+
134
+ describe '#lineno' do
135
+ it 'returns an Integer' do
136
+ e1 = @trip.start
137
+ assert_kind_of Integer, e1.lineno
138
+ end
139
+ end
140
+
141
+ describe '#binding' do
142
+ it 'returns a Binding with access to the local variable "message"' do
143
+ @trip.pause? { |e| e.name == 'call' or e.name == 'return' }
144
+ e1 = @trip.start
145
+ e2 = @trip.resume
146
+ assert_equal 'ping', e2.binding.eval('message')
147
+ end
148
+ end
149
+
150
+ describe '#__binding__' do
151
+ it 'returns a Binding for an instance of Trip::Event' do
152
+ e1 = @trip.start
153
+ assert Trip::Event === e1.__binding__.eval('self')
154
+ end
155
+ end
156
+ end
157
+ end
data/trip.gemspec ADDED
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH << 'lib'
2
+ require 'trip'
3
+ Gem::Specification.new do |g|
4
+ g.name = 'trip'
5
+ g.author = 'rpag'
6
+ g.email = 'rpag@singletonclass.com'
7
+ g.version = Trip::VERSION
8
+ g.summary = 'concurrent ruby tracer'
9
+ g.description = 'concurrent ruby tracer built with Thread#set_trace_func'
10
+ g.files = `git ls-files`.split($/)
11
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rpag
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: concurrent ruby tracer built with Thread#set_trace_func
14
+ email: rpag@singletonclass.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".bundle/config"
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
+ - Gemfile
23
+ - MIT-LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - examples/concurrent.rb
27
+ - examples/time.rb
28
+ - lib/trip.rb
29
+ - lib/trip/event.rb
30
+ - lib/trip/version.rb
31
+ - spec/setup.rb
32
+ - spec/trip_spec.rb
33
+ - trip.gemspec
34
+ homepage:
35
+ licenses: []
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 2.2.2
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: concurrent ruby tracer
57
+ test_files: []
58
+ has_rdoc: