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.
@@ -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