EvalIn
Safely evaluates code (Ruby and others) by sending it through https://eval.in
Example
It's this simple:
require 'eval_in'
result = EvalIn.call 'puts "hello, #{gets}"', stdin: 'world', language: "ruby/mri-2.1"
result.output # => "hello, world\n"
result.exitstatus # => 0
result.url # => "https://eval.in/182711.json"
result.language # => "ruby/mri-2.1"
result.language_friendly # => "Ruby — MRI 2.1"
result.code # => "puts \"hello, \#{gets}\""
result.status # => "OK (0.064 sec real, 0.073 sec wall, 9 MB, 21 syscalls)"
Docs are here.
What languages can this run?
Ruby |
ruby/mri-1.0 ruby/mri-1.8.7 ruby/mri-1.9.3 ruby/mri-2.0.0 ruby/mri-2.1 |
---|---|
C |
c/gcc-4.4.3 c/gcc-4.9.1 |
C++ |
c++/c++11-gcc-4.9.1 c++/gcc-4.4.3 c++/gcc-4.9.1 |
CoffeeScript |
coffeescript/node-0.10.29-coffee-1.7.1 |
Fortran |
fortran/f95-4.4.3 |
Haskell |
haskell/hugs98-sep-2006 |
Io |
io/io-20131204 |
JavaScript |
javascript/node-0.10.29 |
Lua |
lua/lua-5.1.5 lua/lua-5.2.3 |
OCaml |
ocaml/ocaml-4.01.0 |
PHP |
php/php-5.5.14 |
Pascal |
pascal/fpc-2.6.4 |
Perl |
perl/perl-5.20.0 |
Python |
python/cpython-2.7.8 python/cpython-3.4.1 |
Slash |
slash/slash-head |
x86 Assembly |
assembly/nasm-2.07 |
Mocking for non-prod environments
Wiring the mock in
The mock that is provided will need to be set into place (its interface is in the next section).
If the code is directly doing EvalIn.call(...)
, then it is
a hard dependency, and there is no ablity to set the mock into place.
You will need to structure your code such that it receives the eval_in
service as an argument, or looks it up in some configuration or something
(I strongly prefer the former).
This will allow your test environment to give it a mock that tests that it
works in all the edge cases, or is just generally benign (doesn't make real http request in tests),
your dev environment to give it an EvalIn
that looks so close to the real one you wouldn't even know,
and your prod environment to give it the actual EvalIn
that actually uses the service.
If this is still unclear, here is an example:
-
Here
we use whatever
EvalIn
was injected. -
Here
we inject the real
EvalIn
for the prod environment. - Here we inject a mock that does evaluate code for the development environment.
- Here we inject a mock result for the test environment.
The provided mock
For a test or dev env where you don't care about correctness,
just that it does something that looks real,
you can make a mock that has a Result
,
instantiated with any values you care about.
require 'eval_in/mock'
eval_in = EvalIn::Mock.new(result: EvalIn::Result.new(code: 'injected code', output: 'the output'))
eval_in.call('overridden code', language: 'irrelevant')
# => #<EvalIn::Result:0x007fb503a7a5e8
# @code="injected code",
# @exitstatus=-1,
# @language="",
# @language_friendly="",
# @output="the output",
# @status="",
# @url="">
If you want your environment to behave approximately like the real eval_in
,
you can instantiate a mock that knows how to evaluate code locally.
This is necessary, because it doesn't know how to execute these languages
(eval.in does that, it just knows how to talk to eval.in).
So you must provide it with a list of languages and how to execute them
This is probably idea for a dev environment, the results will be the most realistic.
NOTE THAT THIS DOES NOT WORK ON JRUBY
require 'eval_in/mock'
# a mock that can execute Ruby code and C code
eval_in = EvalIn::Mock.new(languages: {
'ruby/mri-2.1' => {program: RbConfig.ruby,
args: []
},
'c/gcc-4.9.1' => {program: RbConfig.ruby,
args: ['-e',
'system "gcc -x c -o /tmp/eval_in_c_example #{ARGV.first}"
exec "/tmp/eval_in_c_example"']
},
})
eval_in.call 'puts "hello from ruby!"; exit 123', language: 'ruby/mri-2.1'
# => #<EvalIn::Result:0x007fb503a7d518
# @code="puts \"hello from ruby!\"; exit 123",
# @exitstatus=123,
# @language="ruby/mri-2.1",
# @language_friendly="ruby/mri-2.1",
# @output="hello from ruby!\n",
# @status="OK (0.072 sec real, 0.085 sec wall, 8 MB, 19 syscalls)",
# @url="https://eval.in/207744.json">
eval_in.call '#include <stdio.h>
int main() {
puts("hello from c!");
}', language: 'c/gcc-4.9.1'
# => #<EvalIn::Result:0x007fb503a850b0
# @code="#include <stdio.h>\nint main() {\n puts(\"hello from c!\");\n}",
# @exitstatus=0,
# @language="c/gcc-4.9.1",
# @language_friendly="c/gcc-4.9.1",
# @output="hello from c!\n",
# @status="OK (0.072 sec real, 0.085 sec wall, 8 MB, 19 syscalls)",
# @url="https://eval.in/207744.json">
You can also provide a callback that will be invoked to handle the request. This is probably ideal for testing more nuanced edge cases that the mock doesn't inherently provide the ability to do.
require 'eval_in/mock'
eval_in = EvalIn::Mock.new on_call: -> code, options { raise EvalIn::RequestError, 'does my code do the right thing in the event of an exception?' }
eval_in.call('code', language: 'any') rescue $! # => #<EvalIn::RequestError: does my code do the right thing in the event of an exception?>
Attribution
Thanks to Charlie Sommerville for making eval-in.
Thanks to Mon Oui, I partially stole the first version of the implementation from his gem cinch-eval-in
Contributing
Fork it, make your changes, send me a pull request. Make sure your code is tested and all tests pass.
Run tests with bundle exec rspec
, run integration tests with bundle exec rspec -t integration
.
Copyright (C) 2014 Josh Cheek <josh.cheek@gmail.com>
This program is free software. It comes without any warranty,
to the extent permitted by applicable law.
You can redistribute it and/or modify it under the terms of the
Do What The Fuck You Want To Public License,
Version 2, as published by Sam Hocevar.
See http://www.wtfpl.net/ for more details.