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.
@@ -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
@@ -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
@@ -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
@@ -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.configure do |config|
8
- config.filter_run_excluding integration: true
9
-
10
- config.include Module.new {
11
- def assert_result(result, attributes)
12
- attributes.each do |key, value|
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
- it 'defaults the error stream to $stderr' do
121
- expect { EvalIn::Result.new a: 1, b: 2 }.to \
122
- output("Unexpected attributes! [:a, :b]\n").to_stderr
123
- end
124
-
125
- it 'has an as_json representation which dumps all its keys' do
126
- result = EvalIn::Result.new(language: 'l',
127
- language_friendly: 'lf',
128
- code: 'c',
129
- output: 'o',
130
- status: 's',
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
- it 'just bubbles the existing error up if it knows the language' do
252
- stub_request(:post, "https://eval.in/").to_return(status: 406)
253
- expect { EvalIn.post_code code, language: 'ruby/mri-2.1' }.to \
254
- raise_error EvalIn::RequestError, /406/
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
- RSpec.describe 'build_result' do
294
- let(:language) { 'some lang' }
295
- let(:language_friendly) { 'some lang friendly' }
296
- let(:code) { 'some code' }
297
- let(:output) { 'some output' }
298
- let(:status) { 'some status' }
299
- let(:url) { 'some url' }
300
- let(:response_json) { {'lang' => language, 'lang_friendly' => language_friendly, 'code' => code, 'output' => output, 'status' => status, 'url' => 'some url'} }
301
-
302
- it 'returns a response for the given response json' do
303
- result = EvalIn.build_result response_json
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
- it 'sets the exit status to 0 when the status does not imply a nonzero exit status' do
329
- result = EvalIn.build_result response_json.merge('status' => 'OK (0.012 sec real, 0.013 sec wall, 7 MB, 22 syscalls)')
330
- expect(result.exitstatus).to eq 0
331
- end
52
+ describe 'toplevel methods' do
53
+ include WebMock::API
332
54
 
333
- it 'sets the exit status to 1 when there is an error' do
334
- result = EvalIn.build_result response_json.merge('status' => 'Forbidden access to file `/usr/local/bin/gem')
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
- RSpec.describe 'fetch_result' do
341
- include WebMock::API
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
- def stub_eval_in(url, options={})
344
- stub_request(:get, url)
345
- .with(headers: {'User-Agent' => options.fetch(:user_agent, 'http://rubygems.org/gems/eval_in')})
346
- .to_return(status: 200, body: json_result)
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
- let(:ruby_result) { {'lang' => 'some lang', 'lang_friendly' => 'some lang friendly', 'code' => 'some code', 'output' => 'some output', 'status' => 'some status'} }
350
- let(:json_result) { JSON.dump ruby_result }
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
- it 'jsonifies the url if it isn\'t already' do
367
- stub_eval_in 'https://eval.in/1.json'
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
- it 'can take a context for the user agent' do
373
- stub_eval_in 'https://eval.in/1.json', user_agent: 'http://rubygems.org/gems/eval_in (c)'
374
- expect(EvalIn.fetch_result('https://eval.in/1', context: 'c').url).to eq 'https://eval.in/1.json'
375
- end
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
- RSpec.describe 'jsonify_url' do
380
- def assert_transforms(initial_url, expected_url)
381
- actual_url = EvalIn.jsonify_url(initial_url)
382
- expect(actual_url).to eq expected_url
383
- end
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
- it 'appends .json to a url that is missing it' do
386
- assert_transforms 'http://eval.in/1', 'http://eval.in/1.json'
387
- assert_transforms 'http://eval.in/1.json', 'http://eval.in/1.json'
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
- assert_transforms 'http://eval.in/1?a=b', 'http://eval.in/1.json?a=b'
390
- assert_transforms 'http://eval.in/1.json?a=b', 'http://eval.in/1.json?a=b'
391
- end
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
- it 'changes .not-json to .json' do
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