eval_in 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +132 -0
- data/eval_in.gemspec +1 -1
- data/lib/eval_in.rb +5 -168
- data/lib/eval_in/client.rb +77 -0
- data/lib/eval_in/constants.rb +37 -0
- data/lib/eval_in/http.rb +46 -0
- data/lib/eval_in/mock.rb +80 -0
- data/lib/eval_in/result.rb +38 -0
- data/spec/client_spec.rb +195 -0
- data/spec/eval_in_spec.rb +91 -371
- data/spec/http_spec.rb +27 -0
- data/spec/mock_spec.rb +168 -0
- data/spec/result_spec.rb +93 -0
- data/spec/spec_helper.rb +23 -0
- metadata +17 -3
- data/lib/eval_in/version.rb +0 -3
data/lib/eval_in/http.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module EvalIn
|
6
|
+
|
7
|
+
# This module contains generic wrappers over net/http
|
8
|
+
# We can't just use Net::HTTP.get, b/c it doesn't use ssl on 1.9.3
|
9
|
+
# https://github.com/ruby/ruby/blob/v2_1_2/lib/net/http.rb#L478-479
|
10
|
+
# https://github.com/ruby/ruby/blob/v1_9_3_547/lib/net/http.rb#L454
|
11
|
+
module HTTP
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def get_request(raw_url, user_agent)
|
15
|
+
generic_request_for raw_url: raw_url,
|
16
|
+
request_type: Net::HTTP::Get,
|
17
|
+
user_agent: user_agent
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_request(raw_url, form_data, user_agent)
|
21
|
+
generic_request_for raw_url: raw_url,
|
22
|
+
request_type: Net::HTTP::Post,
|
23
|
+
user_agent: user_agent,
|
24
|
+
form_data: form_data
|
25
|
+
end
|
26
|
+
|
27
|
+
# stole this out of implementation for post_form https://github.com/ruby/ruby/blob/2afed6eceff2951b949db7ded8167a75b431bad6/lib/net/http.rb#L503
|
28
|
+
# can use this to view the request: http.set_debug_output $stdout
|
29
|
+
def generic_request_for(params)
|
30
|
+
uri = URI params.fetch(:raw_url)
|
31
|
+
path = uri.path
|
32
|
+
path = '/' if path.empty?
|
33
|
+
request = params.fetch(:request_type).new(path)
|
34
|
+
request['User-Agent'] = params[:user_agent] if params.key? :user_agent
|
35
|
+
request.form_data = params[:form_data] if params.key? :form_data
|
36
|
+
request.basic_auth uri.user, uri.password if uri.user
|
37
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: (uri.scheme == 'https')) { |http| http.request request }
|
38
|
+
end
|
39
|
+
|
40
|
+
def jsonify_url(url)
|
41
|
+
uri = URI(url)
|
42
|
+
uri.path = Pathname.new(uri.path).sub_ext('.json').to_s
|
43
|
+
uri.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/eval_in/mock.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'eval_in'
|
4
|
+
require 'eval_in/client'
|
5
|
+
require 'eval_in/result'
|
6
|
+
|
7
|
+
module EvalIn
|
8
|
+
class Mock
|
9
|
+
def initialize(options={})
|
10
|
+
@result = options.fetch :result, nil
|
11
|
+
@languages = options.fetch :languages, Hash.new
|
12
|
+
@on_call = options.fetch(:on_call) { lambda { |*args| @result || evaluate_with_tempfile(*args) } }
|
13
|
+
@on_fetch_result = options.fetch(:on_fetch_result) { lambda { |*args| @result || EvalIn.fetch_result(*args) } }
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(code, options={})
|
17
|
+
language_name = EvalIn::Client.language_or_error_from options
|
18
|
+
@on_call.call(code, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_result(raw_url, options={})
|
22
|
+
@on_fetch_result.call(raw_url, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def evaluate_with_tempfile(code, options={})
|
28
|
+
language_name = EvalIn::Client.language_or_error_from options
|
29
|
+
tempfile = Tempfile.new 'EvalIn-mock'
|
30
|
+
tempfile.write code
|
31
|
+
tempfile.close
|
32
|
+
lang = @languages.fetch language_name
|
33
|
+
program = lang.fetch(:program)
|
34
|
+
args = lang.fetch(:args, []) + [tempfile.path]
|
35
|
+
out, status = open_process_capture_out_and_error(program, args)
|
36
|
+
Result.new output: out,
|
37
|
+
exitstatus: status.exitstatus,
|
38
|
+
language: language_name,
|
39
|
+
language_friendly: language_name,
|
40
|
+
code: code,
|
41
|
+
url: 'https://eval.in/207744.json',
|
42
|
+
status: 'OK (0.072 sec real, 0.085 sec wall, 8 MB, 19 syscalls)'
|
43
|
+
ensure
|
44
|
+
tempfile.unlink if tempfile
|
45
|
+
end
|
46
|
+
|
47
|
+
def open_process_capture_out_and_error(program, args)
|
48
|
+
Open3.capture2e(program, *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
# I legit tried for a couple of hours to get this to work on JRuby, this is attempt 1, it blows up b/c Kernel#spawn won't take in/out/err on JRuby
|
52
|
+
# # Defining it myself since JRuby doesn't seem to implement Open3.capture2e correctly
|
53
|
+
# # Implementation stolen and modified from numerous functions here:
|
54
|
+
# # https://github.com/ruby/ruby/blob/622f31be31b43429dfebe85e8f5bc5c92af5dd1f/lib/open3.rb#L318-351
|
55
|
+
# def open_process_capture_out_and_error(program, args)
|
56
|
+
# in_r, in_w = IO.pipe
|
57
|
+
# out_r, out_w = IO.pipe
|
58
|
+
# in_w.sync = true
|
59
|
+
# pid = spawn(program, *args, in: in_r, out: out_w, err: out_w)
|
60
|
+
# in_r.close
|
61
|
+
# out_w.close
|
62
|
+
# output = out_r.read
|
63
|
+
# Process.wait pid
|
64
|
+
# [output, $?]
|
65
|
+
# ensure
|
66
|
+
# in_w.close
|
67
|
+
# out_r.close
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# This was attempt 2, it blows up b/c I cannot fucking figure out how to get the exit status
|
71
|
+
# def open_process_capture_out_and_error(program, args)
|
72
|
+
# if IO.respond_to? :popen4
|
73
|
+
# pid, stdin, stdout, stderr = IO.popen4('c:\ruby187\bin\ruby.exe')
|
74
|
+
# [stdout.read, $?]
|
75
|
+
# else
|
76
|
+
# Open3.capture2e(program, *args)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module EvalIn
|
4
|
+
# The data structure containing the final result
|
5
|
+
# its attributes default to null-objects for their given type
|
6
|
+
class Result
|
7
|
+
@attribute_names = [:exitstatus, :language, :language_friendly, :code, :output, :status, :url].freeze
|
8
|
+
attr_accessor *@attribute_names
|
9
|
+
class << self
|
10
|
+
attr_reader :attribute_names
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(attributes={})
|
14
|
+
attributes = attributes.dup
|
15
|
+
self.exitstatus = attributes.delete(:exitstatus) || -1
|
16
|
+
self.language = attributes.delete(:language) || ""
|
17
|
+
self.language_friendly = attributes.delete(:language_friendly) || ""
|
18
|
+
self.code = attributes.delete(:code) || ""
|
19
|
+
self.output = attributes.delete(:output) || ""
|
20
|
+
self.status = attributes.delete(:status) || ""
|
21
|
+
self.url = attributes.delete(:url) || ""
|
22
|
+
stderr = attributes.delete(:stderr) || $stderr
|
23
|
+
stderr.puts "Unexpected attributes! #{attributes.keys.inspect}" if attributes.any?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns representation of the result built out of JSON primitives (hash, string, int)
|
27
|
+
# This is useful for composing JSON structures and only rendering the JSON string at the end
|
28
|
+
def as_json
|
29
|
+
self.class.attribute_names.each_with_object Hash.new do |name, attributes|
|
30
|
+
attributes[name.to_s] = public_send name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json
|
35
|
+
JSON.dump(as_json)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
RSpec.describe EvalIn::Client do
|
5
|
+
describe 'post_code' do
|
6
|
+
include WebMock::API
|
7
|
+
|
8
|
+
# ACTUAL RESPONSE:
|
9
|
+
#
|
10
|
+
# HTTP/1.1 302 Found\r
|
11
|
+
# Server: nginx/1.4.6 (Ubuntu)\r
|
12
|
+
# Date: Sun, 24 Aug 2014 05:57:17 GMT\r
|
13
|
+
# Content-Type: text/html;charset=utf-8\r
|
14
|
+
# Content-Length: 0\r
|
15
|
+
# Connection: keep-alive\r
|
16
|
+
# Location: https://eval.in/182584
|
17
|
+
# X-XSS-Protection: 1; mode=block\r
|
18
|
+
# X-Content-Type-Options: nosniff\r
|
19
|
+
# X-Frame-Options: SAMEORIGIN\r
|
20
|
+
# X-Runtime: 0.042154\r
|
21
|
+
# Strict-Transport-Security: max-age=31536000\r
|
22
|
+
# \r
|
23
|
+
def stub_eval_in(data=expected_data)
|
24
|
+
stub_request(:post, url)
|
25
|
+
.with(body: data)
|
26
|
+
.to_return(status: 302, headers: {'Location' => result_location})
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_code(code, options)
|
30
|
+
EvalIn::Client.post_code code, options
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:code) { 'print "hello, #{gets}"' }
|
34
|
+
let(:stdin) { "world" }
|
35
|
+
let(:language) { "ruby/mri-1.9.3" }
|
36
|
+
let(:expected_data) { {"utf8" => "√", "code" => code, "execute" => "on", "lang" => language, "input" => stdin} }
|
37
|
+
let(:result_location) { 'https://eval.in/182584' }
|
38
|
+
let(:url) { "https://eval.in/" }
|
39
|
+
|
40
|
+
it 'posts the data to eval_in with utf8, execute on, and the code/language/input forwarded through' do
|
41
|
+
stub_eval_in expected_data
|
42
|
+
post_code code, stdin: stdin, language: language
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'defaults the user agent to its gem homepage' do
|
46
|
+
stub_request(:post, url)
|
47
|
+
.with(headers: {'User-Agent' => 'http://rubygems.org/gems/eval_in'})
|
48
|
+
.to_return(status: 302, headers: {'Location' => result_location})
|
49
|
+
post_code code, language: language
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can add a context to the user agent' do
|
53
|
+
stub_request(:post, url)
|
54
|
+
.with(headers: {'User-Agent' => 'http://rubygems.org/gems/eval_in (some context)'})
|
55
|
+
.to_return(status: 302, headers: {'Location' => result_location})
|
56
|
+
post_code code, language: language, context: 'some context'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns the redirect location jsonified' do
|
60
|
+
stub_eval_in expected_data
|
61
|
+
result = post_code code, stdin: stdin, language: language
|
62
|
+
expect(result).to eq "#{result_location}.json"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "Doesn't jsonify the redirect location if it already has a json suffix" do
|
66
|
+
result_location << '.json'
|
67
|
+
stub_eval_in expected_data
|
68
|
+
result = post_code code, stdin: stdin, language: language
|
69
|
+
expect(result).to eq result_location
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'sets input to empty string if not provided' do
|
73
|
+
stub_eval_in expected_data.merge('input' => '')
|
74
|
+
post_code code, language: language
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises an ArgumentError error if not given a language' do
|
78
|
+
expect { post_code code, {} }.to raise_error ArgumentError, /language/
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'can override the url' do
|
82
|
+
url.replace "http://example.com"
|
83
|
+
stub_eval_in
|
84
|
+
post_code code, url: 'http://example.com', stdin: stdin, language: language
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'supports basic http auth' do
|
88
|
+
url.replace "http://user:pass@example.com"
|
89
|
+
stub_eval_in
|
90
|
+
post_code code, url: 'http://user:pass@example.com', stdin: stdin, language: language
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when it gets a non-redirect' do
|
94
|
+
it 'informs user of language provided and languages known if language is unknown' do
|
95
|
+
stub_request(:post, "https://eval.in/").to_return(status: 406)
|
96
|
+
expect { post_code code, language: 'unknown-language' }.to \
|
97
|
+
raise_error EvalIn::RequestError, /unknown-language.*?ruby\/mri-2.1/m
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'just bubbles the existing error up if it knows the language' do
|
101
|
+
stub_request(:post, "https://eval.in/").to_return(status: 406)
|
102
|
+
expect { post_code code, language: 'ruby/mri-2.1' }.to \
|
103
|
+
raise_error EvalIn::RequestError, /406/
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
describe 'fetch_result_json' do
|
110
|
+
include WebMock::API
|
111
|
+
|
112
|
+
def stub_eval_in(options={})
|
113
|
+
stub_request(:get, options.fetch(:url))
|
114
|
+
.to_return(status: options.fetch(:status, 200),
|
115
|
+
body: options.fetch(:json_result, json_result))
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_result_json(url)
|
119
|
+
EvalIn::Client.fetch_result_json url
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:ruby_result) { {'lang' => 'some lang', 'lang_friendly' => 'some lang friendly', 'code' => 'some code', 'output' => 'some output', 'status' => 'some status'} }
|
123
|
+
let(:json_result) { JSON.dump ruby_result }
|
124
|
+
|
125
|
+
it 'queries the location, and inflates the json' do
|
126
|
+
stub_eval_in(url: "http://example.com/some-result.json")
|
127
|
+
result = fetch_result_json "http://example.com/some-result.json"
|
128
|
+
expect(result).to match hash_including(ruby_result)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'raises an error when it gets a non-200' do
|
132
|
+
stub_eval_in json_result: '', url: 'http://example.com'
|
133
|
+
expect { fetch_result_json "http://example.com" }.to \
|
134
|
+
raise_error EvalIn::ResultNotFound, %r(http://example.com)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'adds the url to the result' do
|
138
|
+
stub_eval_in url: 'http://example.com'
|
139
|
+
result = fetch_result_json 'http://example.com'
|
140
|
+
expect(result['url']).to eq 'http://example.com'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
describe 'build_result' do
|
146
|
+
def build_result(response_json)
|
147
|
+
result = EvalIn::Client.build_result response_json
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:language) { 'some lang' }
|
151
|
+
let(:language_friendly) { 'some lang friendly' }
|
152
|
+
let(:code) { 'some code' }
|
153
|
+
let(:output) { 'some output' }
|
154
|
+
let(:status) { 'some status' }
|
155
|
+
let(:url) { 'some url' }
|
156
|
+
let(:response_json) { {'lang' => language, 'lang_friendly' => language_friendly, 'code' => code, 'output' => output, 'status' => status, 'url' => 'some url'} }
|
157
|
+
|
158
|
+
it 'returns a response for the given response json' do
|
159
|
+
result = build_result response_json
|
160
|
+
assert_result result,
|
161
|
+
exitstatus: 0,
|
162
|
+
language: language,
|
163
|
+
language_friendly: language_friendly,
|
164
|
+
code: code,
|
165
|
+
output: output,
|
166
|
+
status: status,
|
167
|
+
url: url
|
168
|
+
end
|
169
|
+
|
170
|
+
# exit: https://eval.in/182586.json
|
171
|
+
# raise: https://eval.in/182587.json
|
172
|
+
# in C: https://eval.in/182588.json
|
173
|
+
# Forbidden: https://eval.in/182599.json
|
174
|
+
it 'sets the exit status to that of the program when it is available' do
|
175
|
+
result = build_result response_json.merge('status' => "Exited with error status 123")
|
176
|
+
expect(result.exitstatus).to eq 123
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'sets the exit status to -1 when it is not available' do
|
180
|
+
result = build_result response_json.merge('status' => nil)
|
181
|
+
expect(result.exitstatus).to eq -1
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'sets the exit status to 0 when the status does not imply a nonzero exit status' do
|
185
|
+
result = build_result response_json.merge('status' => 'OK (0.012 sec real, 0.013 sec wall, 7 MB, 22 syscalls)')
|
186
|
+
expect(result.exitstatus).to eq 0
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'sets the exit status to 1 when there is an error' do
|
190
|
+
result = build_result response_json.merge('status' => 'Forbidden access to file `/usr/local/bin/gem')
|
191
|
+
expect(result.exitstatus).to eq 1
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
data/spec/eval_in_spec.rb
CHANGED
@@ -1,398 +1,118 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
2
3
|
|
3
|
-
require 'eval_in'
|
4
|
-
require 'webmock'
|
5
|
-
WebMock.disable_net_connect!
|
6
4
|
|
7
|
-
RSpec.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
expect(result.public_send key).to eq value
|
14
|
-
end
|
5
|
+
RSpec.describe EvalIn do
|
6
|
+
describe 'integration tests', integration: true do
|
7
|
+
around do |spec|
|
8
|
+
WebMock.allow_net_connect!
|
9
|
+
spec.call
|
10
|
+
WebMock.disable_net_connect!
|
15
11
|
end
|
16
|
-
}
|
17
|
-
|
18
|
-
config.after do
|
19
|
-
WebMock.reset!
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
RSpec.describe EvalIn, integration: true do
|
24
|
-
around do |spec|
|
25
|
-
WebMock.allow_net_connect!
|
26
|
-
spec.call
|
27
|
-
WebMock.disable_net_connect!
|
28
|
-
end
|
29
|
-
|
30
|
-
let(:context) { 'eval_in integration test' }
|
31
|
-
|
32
|
-
it 'evaluates Ruby code through eval.in' do
|
33
|
-
result = EvalIn.call 'print "hello, #{gets}"', stdin: "world", language: "ruby/mri-2.1", context: context
|
34
|
-
expect(result.exitstatus ).to eq 0
|
35
|
-
expect(result.language ).to eq "ruby/mri-2.1"
|
36
|
-
expect(result.language_friendly).to eq "Ruby — MRI 2.1"
|
37
|
-
expect(result.code ).to eq 'print "hello, #{gets}"'
|
38
|
-
expect(result.output ).to eq "hello, world"
|
39
|
-
expect(result.status ).to match /OK \([\d.]+ sec real, [\d.]+ sec wall, \d MB, \d+ syscalls\)/
|
40
|
-
expect(result.url ).to match %r(https://eval.in/\d+.json)
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'fetches previous results from eval.in' do
|
44
|
-
result = EvalIn.fetch_result "https://eval.in/147.json"
|
45
|
-
expect(result.exitstatus ).to eq 0
|
46
|
-
expect(result.language ).to eq "ruby/mri-1.9.3"
|
47
|
-
expect(result.language_friendly).to eq "Ruby — MRI 1.9.3"
|
48
|
-
expect(result.code ).to eq %'class Greeter\r\n def initialize(name)\r\n @name = name\r\n end\r\n\r\n def greet\r\n puts \"Hello \#{@name}!\"\r\n end\r\nend\r\n\r\ngreeter = Greeter.new \"Charlie\"\r\ngreeter.greet'
|
49
|
-
expect(result.output ).to eq "Hello Charlie!\n"
|
50
|
-
expect(result.status ).to match /OK \([\d.]+ sec real, [\d.]+ sec wall, \d MB, \d+ syscalls\)/
|
51
|
-
expect(result.url ).to match %r(https://eval.in/147.json)
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'is in sync with known languages' do
|
55
|
-
# iffy solution, but it's simple and works,
|
56
|
-
# Rexml might get taken out of stdlib, so is more likely than this regex to fail in the future,
|
57
|
-
# and I don't want to add dep on Nokogiri (w/ libxml & libxslt) where a small regex works adequately
|
58
|
-
current_known_languages = EvalIn.get_request('https://eval.in', context)
|
59
|
-
.body
|
60
|
-
.each_line
|
61
|
-
.map { |line| line[/option.*?value="([^"]+)"/, 1] }
|
62
|
-
.compact
|
63
|
-
expect(EvalIn::KNOWN_LANGUAGES).to eq current_known_languages
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
RSpec.describe EvalIn::Result do
|
68
|
-
it 'initializes with the provided attributes' do
|
69
|
-
result = EvalIn::Result.new exitstatus: 123,
|
70
|
-
language: 'the language',
|
71
|
-
language_friendly: 'the friendly language',
|
72
|
-
code: 'the code',
|
73
|
-
output: 'the output',
|
74
|
-
status: 'the status'
|
75
|
-
assert_result result,
|
76
|
-
exitstatus: 123,
|
77
|
-
language: 'the language',
|
78
|
-
language_friendly: 'the friendly language',
|
79
|
-
code: 'the code',
|
80
|
-
output: 'the output',
|
81
|
-
status: 'the status'
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'uses sensible type-correct defaults for missing attributes' do
|
85
|
-
assert_result EvalIn::Result.new,
|
86
|
-
exitstatus: -1,
|
87
|
-
language: '',
|
88
|
-
language_friendly: '',
|
89
|
-
code: '',
|
90
|
-
output: '',
|
91
|
-
status: '',
|
92
|
-
url: ''
|
93
|
-
assert_result EvalIn::Result.new(language: nil,
|
94
|
-
language_friendly: nil,
|
95
|
-
code: nil,
|
96
|
-
output: nil,
|
97
|
-
status: nil,
|
98
|
-
url: nil),
|
99
|
-
exitstatus: -1,
|
100
|
-
language: '',
|
101
|
-
language_friendly: '',
|
102
|
-
code: '',
|
103
|
-
output: '',
|
104
|
-
status: '',
|
105
|
-
url: ''
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'doesn\'t mutate the input attributes' do
|
109
|
-
attributes = {status: 'OK'}
|
110
|
-
EvalIn::Result.new attributes
|
111
|
-
expect(attributes).to eq status: 'OK'
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'logs extra attributes to stderr input' do
|
115
|
-
fake_error_stream = StringIO.new
|
116
|
-
EvalIn::Result.new a: 1, b: 2, stderr: fake_error_stream
|
117
|
-
expect(fake_error_stream.string).to eq "Unexpected attributes! [:a, :b]\n"
|
118
|
-
end
|
119
12
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
url: 'u')
|
132
|
-
expect(result.as_json).to eq 'exitstatus' => -1,
|
133
|
-
'language' => 'l',
|
134
|
-
'language_friendly' => 'lf',
|
135
|
-
'code' => 'c',
|
136
|
-
'output' => 'o',
|
137
|
-
'status' => 's',
|
138
|
-
'url' => 'u'
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'has a to_json that works correctly' do
|
142
|
-
result = EvalIn::Result.new(language: 'l',
|
143
|
-
language_friendly: 'lf',
|
144
|
-
code: 'c',
|
145
|
-
output: 'o',
|
146
|
-
status: 's',
|
147
|
-
url: 'u')
|
148
|
-
after_json = JSON.parse(result.to_json)
|
149
|
-
expect(after_json).to eq 'exitstatus' => -1,
|
150
|
-
'language' => 'l',
|
151
|
-
'language_friendly' => 'lf',
|
152
|
-
'code' => 'c',
|
153
|
-
'output' => 'o',
|
154
|
-
'status' => 's',
|
155
|
-
'url' => 'u'
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
|
160
|
-
RSpec.describe 'post_code' do
|
161
|
-
include WebMock::API
|
162
|
-
|
163
|
-
# ACTUAL RESPONSE:
|
164
|
-
#
|
165
|
-
# HTTP/1.1 302 Found\r
|
166
|
-
# Server: nginx/1.4.6 (Ubuntu)\r
|
167
|
-
# Date: Sun, 24 Aug 2014 05:57:17 GMT\r
|
168
|
-
# Content-Type: text/html;charset=utf-8\r
|
169
|
-
# Content-Length: 0\r
|
170
|
-
# Connection: keep-alive\r
|
171
|
-
# Location: https://eval.in/182584
|
172
|
-
# X-XSS-Protection: 1; mode=block\r
|
173
|
-
# X-Content-Type-Options: nosniff\r
|
174
|
-
# X-Frame-Options: SAMEORIGIN\r
|
175
|
-
# X-Runtime: 0.042154\r
|
176
|
-
# Strict-Transport-Security: max-age=31536000\r
|
177
|
-
# \r
|
178
|
-
def stub_eval_in(data=expected_data)
|
179
|
-
stub_request(:post, url)
|
180
|
-
.with(body: data)
|
181
|
-
.to_return(status: 302, headers: {'Location' => result_location})
|
182
|
-
end
|
183
|
-
|
184
|
-
let(:code) { 'print "hello, #{gets}"' }
|
185
|
-
let(:stdin) { "world" }
|
186
|
-
let(:language) { "ruby/mri-1.9.3" }
|
187
|
-
let(:expected_data) { {"utf8" => "√", "code" => code, "execute" => "on", "lang" => language, "input" => stdin} }
|
188
|
-
let(:result_location) { 'https://eval.in/182584' }
|
189
|
-
let(:url) { "https://eval.in/" }
|
190
|
-
|
191
|
-
it 'posts the data to eval_in with utf8, execute on, and the code/language/input forwarded through' do
|
192
|
-
stub_eval_in expected_data
|
193
|
-
EvalIn.post_code code, stdin: stdin, language: language
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'defaults the user agent to its gem homepage' do
|
197
|
-
stub_request(:post, url)
|
198
|
-
.with(headers: {'User-Agent' => 'http://rubygems.org/gems/eval_in'})
|
199
|
-
.to_return(status: 302, headers: {'Location' => result_location})
|
200
|
-
EvalIn.post_code code, language: language
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'can add a context to the user agent' do
|
204
|
-
stub_request(:post, url)
|
205
|
-
.with(headers: {'User-Agent' => 'http://rubygems.org/gems/eval_in (some context)'})
|
206
|
-
.to_return(status: 302, headers: {'Location' => result_location})
|
207
|
-
EvalIn.post_code code, language: language, context: 'some context'
|
208
|
-
end
|
209
|
-
|
210
|
-
it 'returns the redirect location jsonified' do
|
211
|
-
stub_eval_in expected_data
|
212
|
-
result = EvalIn.post_code code, stdin: stdin, language: language
|
213
|
-
expect(result).to eq "#{result_location}.json"
|
214
|
-
end
|
215
|
-
|
216
|
-
it "Doesn't jsonify the redirect location if it already has a json suffix" do
|
217
|
-
result_location << '.json'
|
218
|
-
stub_eval_in expected_data
|
219
|
-
result = EvalIn.post_code code, stdin: stdin, language: language
|
220
|
-
expect(result).to eq result_location
|
221
|
-
end
|
222
|
-
|
223
|
-
it 'sets input to empty string if not provided' do
|
224
|
-
stub_eval_in expected_data.merge('input' => '')
|
225
|
-
EvalIn.post_code code, language: language
|
226
|
-
end
|
227
|
-
|
228
|
-
it 'raises an ArgumentError error if not given a language' do
|
229
|
-
expect { EvalIn.post_code code, {} }.to raise_error ArgumentError, /language/
|
230
|
-
end
|
231
|
-
|
232
|
-
it 'can override the url' do
|
233
|
-
url.replace "http://example.com"
|
234
|
-
stub_eval_in
|
235
|
-
EvalIn.post_code code, url: 'http://example.com', stdin: stdin, language: language
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'supports basic http auth' do
|
239
|
-
url.replace "http://user:pass@example.com"
|
240
|
-
stub_eval_in
|
241
|
-
EvalIn.post_code code, url: 'http://user:pass@example.com', stdin: stdin, language: language
|
242
|
-
end
|
243
|
-
|
244
|
-
context 'when it gets a non-redirect' do
|
245
|
-
it 'informs user of language provided and languages known if language is unknown' do
|
246
|
-
stub_request(:post, "https://eval.in/").to_return(status: 406)
|
247
|
-
expect { EvalIn.post_code code, language: 'unknown-language' }.to \
|
248
|
-
raise_error EvalIn::RequestError, /unknown-language.*?ruby\/mri-2.1/m
|
13
|
+
let(:context) { 'eval_in integration test' }
|
14
|
+
|
15
|
+
example 'using .call to evaluate Ruby code' do
|
16
|
+
result = EvalIn.call 'print "hello, #{gets}"', stdin: "world", language: "ruby/mri-2.1", context: context
|
17
|
+
expect(result.exitstatus ).to eq 0
|
18
|
+
expect(result.language ).to eq "ruby/mri-2.1"
|
19
|
+
expect(result.language_friendly).to eq "Ruby — MRI 2.1"
|
20
|
+
expect(result.code ).to eq 'print "hello, #{gets}"'
|
21
|
+
expect(result.output ).to eq "hello, world"
|
22
|
+
expect(result.status ).to match success_status_regex
|
23
|
+
expect(result.url ).to match %r(https://eval.in/\d+.json)
|
249
24
|
end
|
250
25
|
|
251
|
-
|
252
|
-
|
253
|
-
expect
|
254
|
-
|
26
|
+
example 'using .fetch_result to fetch a known result from eval.in' do
|
27
|
+
result = EvalIn.fetch_result "https://eval.in/147.json"
|
28
|
+
expect(result.exitstatus ).to eq 0
|
29
|
+
expect(result.language ).to eq "ruby/mri-1.9.3"
|
30
|
+
expect(result.language_friendly).to eq "Ruby — MRI 1.9.3"
|
31
|
+
expect(result.code ).to eq %'class Greeter\r\n def initialize(name)\r\n @name = name\r\n end\r\n\r\n def greet\r\n puts \"Hello \#{@name}!\"\r\n end\r\nend\r\n\r\ngreeter = Greeter.new \"Charlie\"\r\ngreeter.greet'
|
32
|
+
expect(result.output ).to eq "Hello Charlie!\n"
|
33
|
+
expect(result.status ).to match success_status_regex
|
34
|
+
expect(result.url ).to match %r(https://eval.in/147.json)
|
255
35
|
end
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
RSpec.describe 'fetch_result_json' do
|
262
|
-
include WebMock::API
|
263
|
-
|
264
|
-
def stub_eval_in(options={})
|
265
|
-
stub_request(:get, options.fetch(:url))
|
266
|
-
.to_return(status: options.fetch(:status, 200),
|
267
|
-
body: options.fetch(:json_result, json_result))
|
268
|
-
end
|
269
|
-
|
270
|
-
let(:ruby_result) { {'lang' => 'some lang', 'lang_friendly' => 'some lang friendly', 'code' => 'some code', 'output' => 'some output', 'status' => 'some status'} }
|
271
|
-
let(:json_result) { JSON.dump ruby_result }
|
272
|
-
|
273
|
-
it 'queries the location, and inflates the json' do
|
274
|
-
stub_eval_in(url: "http://example.com/some-result.json")
|
275
|
-
result = EvalIn.fetch_result_json "http://example.com/some-result.json"
|
276
|
-
expect(result).to match hash_including(ruby_result)
|
277
|
-
end
|
278
|
-
|
279
|
-
it 'raises an error when it gets a non-200' do
|
280
|
-
stub_eval_in json_result: '', url: 'http://example.com'
|
281
|
-
expect { EvalIn.fetch_result_json "http://example.com" }.to \
|
282
|
-
raise_error EvalIn::ResultNotFound, %r(http://example.com)
|
283
|
-
end
|
284
|
-
|
285
|
-
it 'adds the url to the result' do
|
286
|
-
stub_eval_in url: 'http://example.com'
|
287
|
-
result = EvalIn.fetch_result_json 'http://example.com'
|
288
|
-
expect(result['url']).to eq 'http://example.com'
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
36
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
assert_result result,
|
305
|
-
exitstatus: 0,
|
306
|
-
language: language,
|
307
|
-
language_friendly: language_friendly,
|
308
|
-
code: code,
|
309
|
-
output: output,
|
310
|
-
status: status,
|
311
|
-
url: url
|
37
|
+
example 'ensure the KNOWN_LANGUAGES are up to date' do
|
38
|
+
# iffy solution, but it's simple and works,
|
39
|
+
# Rexml might get taken out of stdlib, so is more likely than this regex to fail in the future,
|
40
|
+
# and I don't want to add dep on Nokogiri (w/ libxml & libxslt) where a small regex works adequately
|
41
|
+
current_known_languages = EvalIn::HTTP.get_request('https://eval.in', context)
|
42
|
+
.body
|
43
|
+
.each_line
|
44
|
+
.map { |line| line[/option.*?value="([^"]+)"/, 1] }
|
45
|
+
.compact
|
46
|
+
expect(EvalIn::KNOWN_LANGUAGES).to eq current_known_languages
|
47
|
+
end
|
312
48
|
end
|
313
49
|
|
314
|
-
# exit: https://eval.in/182586.json
|
315
|
-
# raise: https://eval.in/182587.json
|
316
|
-
# in C: https://eval.in/182588.json
|
317
|
-
# Forbidden: https://eval.in/182599.json
|
318
|
-
it 'sets the exit status to that of the program when it is available' do
|
319
|
-
result = EvalIn.build_result response_json.merge('status' => "Exited with error status 123")
|
320
|
-
expect(result.exitstatus).to eq 123
|
321
|
-
end
|
322
50
|
|
323
|
-
it 'sets the exit status to -1 when it is not available' do
|
324
|
-
result = EvalIn.build_result response_json.merge('status' => nil)
|
325
|
-
expect(result.exitstatus).to eq -1
|
326
|
-
end
|
327
51
|
|
328
|
-
|
329
|
-
|
330
|
-
expect(result.exitstatus).to eq 0
|
331
|
-
end
|
52
|
+
describe 'toplevel methods' do
|
53
|
+
include WebMock::API
|
332
54
|
|
333
|
-
|
334
|
-
|
335
|
-
expect(result.exitstatus).to eq 1
|
336
|
-
end
|
337
|
-
end
|
55
|
+
let(:ruby_result) { {'lang' => 'some lang', 'lang_friendly' => 'some lang friendly', 'code' => 'some code', 'output' => 'some output', 'status' => 'some status'} }
|
56
|
+
let(:json_result) { JSON.dump ruby_result }
|
338
57
|
|
58
|
+
def stub_eval_in(url, options={})
|
59
|
+
stub_request(:get, url)
|
60
|
+
.with(headers: {'User-Agent' => options.fetch(:user_agent, 'http://rubygems.org/gems/eval_in')})
|
61
|
+
.to_return(status: 200, body: json_result)
|
62
|
+
end
|
339
63
|
|
340
|
-
|
341
|
-
|
64
|
+
describe '.call' do
|
65
|
+
it 'posts code through eval.in, following the redirect to return an EvalIn::Result' do
|
66
|
+
post_url = 'http://example.com/fake_post_endpoint'
|
67
|
+
get_url = 'http://example.com/fake_get_endpoint.json'
|
68
|
+
context = 'SOME CONTEXT'
|
69
|
+
user_agent = "http://rubygems.org/gems/eval_in (#{context})"
|
342
70
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
end
|
71
|
+
# stub the post, verifies the context and url got passed through, redirects to the get
|
72
|
+
stub_request(:post, post_url)
|
73
|
+
.with(headers: {'User-Agent' => user_agent})
|
74
|
+
.to_return(status: 302, headers: {'location' => get_url}, body: json_result)
|
348
75
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
it 'wraps fetch_result_json and build_result' do
|
353
|
-
url = 'https://eval.in/1.json'
|
354
|
-
stub_eval_in url
|
355
|
-
result = EvalIn.fetch_result url
|
356
|
-
assert_result result,
|
357
|
-
exitstatus: 0,
|
358
|
-
language: ruby_result['lang'],
|
359
|
-
language_friendly: ruby_result['lang_friendly'],
|
360
|
-
code: ruby_result['code'],
|
361
|
-
output: ruby_result['output'],
|
362
|
-
status: ruby_result['status'],
|
363
|
-
url: url
|
364
|
-
end
|
76
|
+
# stub the get to return the mock result
|
77
|
+
stub_eval_in(get_url, user_agent: user_agent)
|
365
78
|
|
366
|
-
|
367
|
-
|
368
|
-
expect(EvalIn.fetch_result('https://eval.in/1').url).to eq 'https://eval.in/1.json'
|
369
|
-
expect(EvalIn.fetch_result('https://eval.in/1.json').url).to eq 'https://eval.in/1.json'
|
370
|
-
end
|
79
|
+
# make the request, posting the code, following the redirect, parsing the json, turning it into a Result
|
80
|
+
result = EvalIn.call 'print "hello, #{gets}"', language: "ruby/mri-2.1", context: context, url: post_url
|
371
81
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
end
|
82
|
+
# Just verify that one of the expected values is there,
|
83
|
+
# all the specifics are better tested in .fetch_result and the integration tests
|
84
|
+
# this is just a quick wiring test with a "data looks about right"
|
85
|
+
expect(result.language).to eq ruby_result['lang']
|
86
|
+
end
|
87
|
+
end
|
377
88
|
|
378
89
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
90
|
+
describe '.fetch_result' do
|
91
|
+
it 'fetches the json at the provided url and returns a result' do
|
92
|
+
url = 'https://eval.in/1.json'
|
93
|
+
stub_eval_in url
|
94
|
+
result = EvalIn.fetch_result url
|
95
|
+
assert_result result,
|
96
|
+
exitstatus: 0,
|
97
|
+
language: ruby_result['lang'],
|
98
|
+
language_friendly: ruby_result['lang_friendly'],
|
99
|
+
code: ruby_result['code'],
|
100
|
+
output: ruby_result['output'],
|
101
|
+
status: ruby_result['status'],
|
102
|
+
url: url
|
103
|
+
end
|
384
104
|
|
385
|
-
|
386
|
-
|
387
|
-
|
105
|
+
it 'jsonifies the url if it isn\'t already' do
|
106
|
+
stub_eval_in 'https://eval.in/1.json'
|
107
|
+
expect(EvalIn.fetch_result('https://eval.in/1').url).to eq 'https://eval.in/1.json'
|
108
|
+
expect(EvalIn.fetch_result('https://eval.in/1.json').url).to eq 'https://eval.in/1.json'
|
109
|
+
end
|
388
110
|
|
389
|
-
|
390
|
-
|
391
|
-
|
111
|
+
it 'can take a context for the user agent' do
|
112
|
+
stub_eval_in 'https://eval.in/1.json', user_agent: 'http://rubygems.org/gems/eval_in (c)'
|
113
|
+
expect(EvalIn.fetch_result('https://eval.in/1', context: 'c').url).to eq 'https://eval.in/1.json'
|
114
|
+
end
|
392
115
|
|
393
|
-
|
394
|
-
assert_transforms 'http://eval.in/1.xml', 'http://eval.in/1.json'
|
395
|
-
assert_transforms 'http://eval.in/1.html', 'http://eval.in/1.json'
|
396
|
-
assert_transforms 'http://eval.in/1.html?a=b', 'http://eval.in/1.json?a=b'
|
116
|
+
end
|
397
117
|
end
|
398
118
|
end
|