endoscope 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +7 -0
- data/bin/endoscope +12 -0
- data/bin/patient +36 -0
- data/endoscope.gemspec +26 -0
- data/lib/endoscope.rb +2 -0
- data/lib/endoscope/agent.rb +93 -0
- data/lib/endoscope/cli.rb +114 -0
- data/lib/endoscope/transport.rb +80 -0
- data/lib/endoscope/version.rb +3 -0
- data/spec/endoscope_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 90f6bdfe035c6da330ff7889c78313cdea6f654c
|
4
|
+
data.tar.gz: 866e177d694c6165eac866c813eeb962d3930151
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: df4505b45883aa0583255228f1f21676531a8f5e4b8b41ecc0c115e5a0c81dad1e75fe5aadcda9451afe326a23a12e22ad880538caaade657c29ae2714adc9c5
|
7
|
+
data.tar.gz: c074d13f39ad2a9fdf5a0873449072ca081ad2474c6e985b9fe8c4dfea829ddfc3f082610b98eab6c313062d024b14316b03f1e52ce5453fdf1a687243044276
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
vendor/bundle/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Mathieu Ravaux
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Endoscope
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/github/preplay/endoscope.png)](https://codeclimate.com/github/preplay/endoscope)
|
4
|
+
[![Build Status](https://travis-ci.org/preplay/endoscope.svg?branch=master)](https://travis-ci.org/preplay/endoscope)
|
5
|
+
|
6
|
+
Remote shell for live interaction with Ruby processes
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'endoscope'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install endoscope
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
You need to start the endoscope agent when your application boots up, naming this process.
|
25
|
+
|
26
|
+
For example, in a Rails app this could go in `config/initializers/endoscope.rb`
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require "endoscope"
|
30
|
+
|
31
|
+
process_name = if dyno = ENV['DYNO'] || ENV['PS']
|
32
|
+
Process.pid == 2 ? dyno : "#{dyno}-child-#{Process.pid}"
|
33
|
+
else
|
34
|
+
progname = $PROGRAM_NAME.gsub(Rails.root.to_s + '/', '')
|
35
|
+
"#{progname}:#{Process.pid}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# using ENV['ENDOSCOPE_REDIS_URL'] and ENV['ENDOSCOPE_REDIS_NAMESPACE']
|
39
|
+
Endoscope::Agent.new(process_name).start
|
40
|
+
```
|
41
|
+
|
42
|
+
You can also use a redis connection configuration as expected by the redis gem.
|
43
|
+
|
44
|
+
You can then use the Endoscope shell to interact with your app.
|
45
|
+
For example, on a running Heroku app comprising of web, sidekiq and
|
46
|
+
sidekiq-scheduler dynos:
|
47
|
+
|
48
|
+
```
|
49
|
+
bundle exec endoscope
|
50
|
+
>> 40 + 2
|
51
|
+
40 + 2
|
52
|
+
Sending command 40 + 2...
|
53
|
+
|
54
|
+
From web.1-child-6 :
|
55
|
+
42
|
56
|
+
|
57
|
+
From web.2-child-9 :
|
58
|
+
42
|
59
|
+
|
60
|
+
From web.1-child-9 :
|
61
|
+
42
|
62
|
+
|
63
|
+
From worker.1 :
|
64
|
+
42
|
65
|
+
|
66
|
+
From web.1-child-13 :
|
67
|
+
42
|
68
|
+
|
69
|
+
From web.2-child-6 :
|
70
|
+
42
|
71
|
+
|
72
|
+
From web.2-child-13 :
|
73
|
+
42
|
74
|
+
|
75
|
+
From scheduler.1 :
|
76
|
+
42
|
77
|
+
|
78
|
+
```
|
79
|
+
|
80
|
+
You can direct your commands at select process types or processes via
|
81
|
+
the `use command`
|
82
|
+
|
83
|
+
```
|
84
|
+
>> use web
|
85
|
+
Now adressing commands to processes listening for "web".
|
86
|
+
>> use worker.1
|
87
|
+
Now adressing commands to processes listening for "worker.1".
|
88
|
+
>> use all
|
89
|
+
Now adressing commands to processes listening for "all".
|
90
|
+
```
|
91
|
+
|
92
|
+
## Common uses
|
93
|
+
* retrieving garbage collection stats
|
94
|
+
* Taking thread dumps
|
95
|
+
* Re-open classes, redefining method to test changes / add logging without needing to commit and re-deploy
|
96
|
+
* adjust logging verbosity
|
97
|
+
* toggle features while debugging to narrow down the possible error causes
|
98
|
+
|
99
|
+
|
100
|
+
## Notes
|
101
|
+
|
102
|
+
* The endoscope communication happens securely over Redis PubSub.
|
103
|
+
* The commands are evaluated in the top level binding of the instrumented program.
|
104
|
+
* stdout and stderr outputs are captured while the command is evaluated.
|
105
|
+
* Commands evaluation exceptions are caught and reported via the endoscope.
|
106
|
+
* Commands evaluation is protected by an execution timeout of 10 seconds.
|
107
|
+
* The agent starts a ruby thread with no overhead while not evaluating remote commands.
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
1. Fork it ( https://github.com/preplay/endoscope/fork )
|
112
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
113
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
114
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
115
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/endoscope
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/endoscope/cli"
|
4
|
+
|
5
|
+
if ARGV.count > 2
|
6
|
+
abort "endoscope [redis_url] [namespace] (or use ENDOSCOPE_REDIS_URL and/or ENDOSCOPE_REDIS_NAMESPACE ENV variables)"
|
7
|
+
end
|
8
|
+
|
9
|
+
redis_url, namespace = *ARGV
|
10
|
+
redis_url ||= ENV['ENDOSCOPE_REDIS_URL']
|
11
|
+
namespace ||= ENV['ENDOSCOPE_REDIS_NAMESPACE']
|
12
|
+
Endoscope::CLI.new({url: redis_url, namespace: namespace}).start
|
data/bin/patient
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/endoscope"
|
4
|
+
|
5
|
+
Endoscope::Agent.new("patient.1", {url: "redis://127.0.0.1:6379"}).start
|
6
|
+
|
7
|
+
module Patient
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def live
|
11
|
+
say_hello
|
12
|
+
loop do
|
13
|
+
think
|
14
|
+
talk
|
15
|
+
sleep
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def say_hello
|
20
|
+
puts "Hello from a sample instrumented process"
|
21
|
+
end
|
22
|
+
|
23
|
+
def think
|
24
|
+
1000.times { 42 * 42 }
|
25
|
+
end
|
26
|
+
|
27
|
+
def talk
|
28
|
+
print "."
|
29
|
+
end
|
30
|
+
|
31
|
+
def sleep
|
32
|
+
Kernel.sleep 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Patient.live
|
data/endoscope.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'endoscope/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "endoscope"
|
8
|
+
spec.version = Endoscope::VERSION
|
9
|
+
spec.authors = ["Mathieu Ravaux"]
|
10
|
+
spec.email = ["mathieu.ravaux@gmail.com"]
|
11
|
+
spec.summary = "Remote shell for live interaction with Ruby processes"
|
12
|
+
spec.description = ""
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "redis"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/lib/endoscope.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# TODO:
|
2
|
+
# * allow configuring the transport
|
3
|
+
# * allow configuring the namesapce
|
4
|
+
|
5
|
+
require_relative "transport"
|
6
|
+
|
7
|
+
require "timeout"
|
8
|
+
require "stringio"
|
9
|
+
|
10
|
+
module Endoscope
|
11
|
+
class Agent
|
12
|
+
ENDOSCOPE = "endoscope".freeze
|
13
|
+
|
14
|
+
attr_reader :dyno_name, :redis_options
|
15
|
+
|
16
|
+
def initialize(dyno_name, redis_options)
|
17
|
+
@dyno_name = dyno_name
|
18
|
+
@redis_options = redis_options || default_redis_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_redis_options
|
22
|
+
{
|
23
|
+
url: ENV['ENDOSCOPE_REDIS_URL'] || 'redis://127.0.0.1:6379/',
|
24
|
+
namespace: ENV['ENDOSCOPE_REDIS_NAMESPACE']
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
Thread.new(&method(:agent_listener))
|
30
|
+
end
|
31
|
+
|
32
|
+
def agent_listener
|
33
|
+
Thread.current[:name] = ENDOSCOPE
|
34
|
+
begin
|
35
|
+
wait_for_commands
|
36
|
+
rescue => e
|
37
|
+
puts e.inspect
|
38
|
+
puts e.backtrace.join("\n")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def wait_for_commands
|
43
|
+
transport = Transport.new(redis_options)
|
44
|
+
transport.wait_for_commands(dyno_name) do |command|
|
45
|
+
command_received(command)
|
46
|
+
end
|
47
|
+
rescue Transport::ConnectionError => error
|
48
|
+
puts "ns=endoscope at=wait_for_commands error=#{error} reconnect_in=1s"
|
49
|
+
sleep 1
|
50
|
+
retry
|
51
|
+
end
|
52
|
+
|
53
|
+
def command_received(command)
|
54
|
+
puts "ns=endoscope at=command_received"
|
55
|
+
to_eval = command.fetch('command')
|
56
|
+
result = evaluate(to_eval)
|
57
|
+
Transport.new(redis_options).publish_response(command, dyno_name, result)
|
58
|
+
end
|
59
|
+
|
60
|
+
EvalTimeout = Class.new(Timeout::Error)
|
61
|
+
|
62
|
+
def evaluate(ruby)
|
63
|
+
capture_streams do |out|
|
64
|
+
begin
|
65
|
+
Timeout.timeout(10, EvalTimeout) do
|
66
|
+
# rubocop:disable Eval
|
67
|
+
res = eval(ruby, TOPLEVEL_BINDING, 'remote_command')
|
68
|
+
# rubocop:enable Eval
|
69
|
+
out.puts res.inspect
|
70
|
+
end
|
71
|
+
rescue Exception => e
|
72
|
+
out.puts(e.inspect, *e.backtrace)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def capture_streams
|
78
|
+
$old_stdout = $stdout
|
79
|
+
$old_stderr = $stderr
|
80
|
+
|
81
|
+
out = StringIO.new
|
82
|
+
$stdout = out
|
83
|
+
$stderr = out
|
84
|
+
yield(out)
|
85
|
+
|
86
|
+
out.rewind
|
87
|
+
out.read
|
88
|
+
ensure
|
89
|
+
$stdout = $old_stdout
|
90
|
+
$stderr = $old_stderr
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative "../endoscope"
|
2
|
+
require_relative "transport"
|
3
|
+
|
4
|
+
require "set"
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
module Endoscope
|
8
|
+
class CLI
|
9
|
+
attr_accessor :dyno_selector, :issued, :transport_opts
|
10
|
+
|
11
|
+
def initialize(transport_opts=nil)
|
12
|
+
@transport_opts = transport_opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def start(dyno_selector = 'all')
|
16
|
+
@dyno_selector = dyno_selector
|
17
|
+
@issued = Set.new
|
18
|
+
start_responses_printing_thread
|
19
|
+
transport
|
20
|
+
start_shell
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_shell
|
24
|
+
begin
|
25
|
+
require "ripl"
|
26
|
+
require "ripl/ripper"
|
27
|
+
rescue LoadError => err
|
28
|
+
puts err.message
|
29
|
+
abort("\nYou need to run:\n\ngem install ripl ripl-ripper\n\nThen launch this command again.")
|
30
|
+
end
|
31
|
+
|
32
|
+
cli = self
|
33
|
+
Ripl::Shell.send(:define_method, :loop_eval) { |str| cli.eval_(str) }
|
34
|
+
Ripl::Shell.send(:define_method, :print_result) { |_result| }
|
35
|
+
Ripl.start( argv: [], irbrc: false, riplrc: false, ripper_prompt: ' | ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_responses_printing_thread
|
39
|
+
@responses_thread = Thread.new(&method(:responses_printer))
|
40
|
+
end
|
41
|
+
|
42
|
+
def responses_printer
|
43
|
+
Thread.current[:name] = 'endoscope-responses-printing'
|
44
|
+
listen_to_command_responses
|
45
|
+
end
|
46
|
+
|
47
|
+
def listen_to_command_responses
|
48
|
+
transport = Transport.new(transport_opts)
|
49
|
+
transport.listen_to_responses do |response|
|
50
|
+
handle_response(response)
|
51
|
+
end
|
52
|
+
rescue Redis::TimeoutError => _
|
53
|
+
retry
|
54
|
+
rescue => err
|
55
|
+
puts err.inspect
|
56
|
+
puts err.backtrace.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_response(res)
|
60
|
+
#p res
|
61
|
+
#p issued
|
62
|
+
#p res['id']
|
63
|
+
return unless issued.include?(res['id'])
|
64
|
+
puts "From #{res['dyno_name']} :\n#{res['result']}\n\n"
|
65
|
+
$stdout.flush
|
66
|
+
end
|
67
|
+
|
68
|
+
def repl
|
69
|
+
catch(:break) do
|
70
|
+
puts "\n\n ---\nRemote console ready:\n\n"
|
71
|
+
$stdout.flush
|
72
|
+
loop { re($stdout) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def re(out = $stdout)
|
77
|
+
command = read
|
78
|
+
eval_(command, out)
|
79
|
+
rescue Interrupt
|
80
|
+
throw(:break)
|
81
|
+
end
|
82
|
+
|
83
|
+
def eval_(command, out = $stdout)
|
84
|
+
case command
|
85
|
+
when 'exit'
|
86
|
+
throw(:break)
|
87
|
+
when /^use /
|
88
|
+
@dyno_selector = command.gsub('use ', '').strip
|
89
|
+
puts "Now adressing commands to processes listening for #{dyno_selector.inspect}."
|
90
|
+
else
|
91
|
+
send_command(command, out) unless command.nil? || command.strip == ''
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def read
|
96
|
+
read = gets
|
97
|
+
read && read.chomp
|
98
|
+
end
|
99
|
+
|
100
|
+
def transport
|
101
|
+
@transport ||= begin
|
102
|
+
Transport.new(transport_opts)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def send_command(command, _out = $stdout)
|
107
|
+
puts "Sending command #{command}..."
|
108
|
+
command_id = SecureRandom.uuid
|
109
|
+
issued << command_id
|
110
|
+
|
111
|
+
transport.send_command(command_id, command, dyno_selector)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative "../endoscope"
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Endoscope
|
7
|
+
class Transport
|
8
|
+
ConnectionError = Class.new(RuntimeError)
|
9
|
+
|
10
|
+
attr_reader :namespace, :redis_opts
|
11
|
+
def initialize(opts)
|
12
|
+
@namespace = opts.delete(:namespace) || "endoscope"
|
13
|
+
@redis_opts = opts
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait_for_commands(dyno_name)
|
17
|
+
channels = command_channels(dyno_name)
|
18
|
+
connection.subscribe(*channels) do |on|
|
19
|
+
on.message do |_channel, message|
|
20
|
+
# puts "##{channel}: #{message}"
|
21
|
+
command = JSON.parse(message)
|
22
|
+
yield(command)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue Redis::BaseConnectionError => error
|
26
|
+
raise ConnectionError, error.message, error
|
27
|
+
end
|
28
|
+
|
29
|
+
def send_command(command_id, command, dyno_selector)
|
30
|
+
channel = requests_channel(dyno_selector)
|
31
|
+
connection.publish(channel, JSON.generate(
|
32
|
+
id: command_id,
|
33
|
+
command: command,
|
34
|
+
channel: channel
|
35
|
+
))
|
36
|
+
end
|
37
|
+
|
38
|
+
def publish_response(command, dyno_name, result)
|
39
|
+
connection.publish(responses_channel, JSON.generate(
|
40
|
+
id: command.fetch('id'),
|
41
|
+
command: command.fetch('command'),
|
42
|
+
dyno_name: dyno_name,
|
43
|
+
result: result
|
44
|
+
))
|
45
|
+
end
|
46
|
+
|
47
|
+
def listen_to_responses
|
48
|
+
connection.subscribe(responses_channel) do |on|
|
49
|
+
on.message do |_channel_name, message|
|
50
|
+
response = JSON.parse(message)
|
51
|
+
yield(response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def connection
|
60
|
+
@connection ||= Redis.connect(redis_opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
ALL = "all".freeze
|
64
|
+
def command_channels(dyno)
|
65
|
+
type = dyno.split('.', 2).first
|
66
|
+
[requests_channel(type), requests_channel(dyno), requests_channel(ALL)]
|
67
|
+
end
|
68
|
+
|
69
|
+
def requests_channel(selector)
|
70
|
+
"#{namespace}:requests:#{selector}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def responses_channel
|
74
|
+
"#{namespace}:responses"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
describe Endoscope do
|
6
|
+
it 'has a version number' do
|
7
|
+
expect(Endoscope::VERSION).not_to be nil
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with a patient process running the Endoscope agent" do
|
11
|
+
let(:bin) { File.expand_path("../../bin", __FILE__) }
|
12
|
+
|
13
|
+
before do
|
14
|
+
@patient = IO.popen(File.join(bin, 'patient'))
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Process.kill "TERM", @patient.pid
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'allows an endoscope process to evaluate ruby code inside of a patient process and show the result' do
|
22
|
+
q = Queue.new
|
23
|
+
|
24
|
+
Open3.popen2(File.join(bin, 'endoscope')) do |endo_in, endo_out, _endo_wait|
|
25
|
+
Thread.new { endo_out.each_line { |l| q.push l.chomp } }
|
26
|
+
|
27
|
+
endo_in.puts "$0"
|
28
|
+
|
29
|
+
Timeout.timeout(5) do
|
30
|
+
expect(q.pop).to eql ">> $0" # user input
|
31
|
+
expect(q.pop).to eql ">> Sending command $0..." # command confirmation
|
32
|
+
expect(q.pop).to eql "From patient.1 :" # response banner
|
33
|
+
expect(q.pop).to include "endoscope/bin/patient" # response contents
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: endoscope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mathieu Ravaux
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: ''
|
70
|
+
email:
|
71
|
+
- mathieu.ravaux@gmail.com
|
72
|
+
executables:
|
73
|
+
- endoscope
|
74
|
+
- patient
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- ".rspec"
|
80
|
+
- ".travis.yml"
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- bin/endoscope
|
86
|
+
- bin/patient
|
87
|
+
- endoscope.gemspec
|
88
|
+
- lib/endoscope.rb
|
89
|
+
- lib/endoscope/agent.rb
|
90
|
+
- lib/endoscope/cli.rb
|
91
|
+
- lib/endoscope/transport.rb
|
92
|
+
- lib/endoscope/version.rb
|
93
|
+
- spec/endoscope_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: ''
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.2.2
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Remote shell for live interaction with Ruby processes
|
119
|
+
test_files:
|
120
|
+
- spec/endoscope_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
has_rdoc:
|