eval_in 0.1.6 → 0.2.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 +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
|