Skip to content

Commit

Permalink
Templating added.
Browse files Browse the repository at this point in the history
  • Loading branch information
brgmnn committed Jul 31, 2016
1 parent aac687e commit 2a964c6
Show file tree
Hide file tree
Showing 26 changed files with 335 additions and 67 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
code/
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Documentation:
Enabled: false
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Sandboxy

## Templates

A template is just an ERB template that wraps a solution. It's useful for
writing language specific test cases for that solution.

```
templates/#{language}/#{slug}/run.#{ext}.erb
```

For example a factorial template for a Javascript problem with the slug
`factorial` would have the path `templates/javascript/factorial/run.js.erb`.

The solution is inserted where `<%= @solution %>` is placed in the template.
When writing test cases, in order to score a solution automatically print to
stdout either `'<%= @id %> test passed'` for a successfully passed test, or
`'<%= @id %> test failed'` for a failed test, one per line. After the solution
is run the total number of passed and failed tests will be stored in the
results file. The `@id` variable is a random 30 digit integer generated for
each solution.

1 change: 1 addition & 0 deletions config/containers/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NetworkDisabled: true
14 changes: 14 additions & 0 deletions config/language.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
javascript:
stages:
- run:
command: nodejs

python:
stages:
- run:
command: python

ruby:
stages:
- run:
command: ruby
3 changes: 3 additions & 0 deletions examples/code/javascript-candidate/fibonacci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function fibonacci(n) {
return n;
}
1 change: 1 addition & 0 deletions examples/code/javascript-candidate/hello-world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("hello world");
1 change: 1 addition & 0 deletions examples/code/python-candidate/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print "hello world"
1 change: 1 addition & 0 deletions examples/code/ruby-candidate/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
puts "hello world"
32 changes: 32 additions & 0 deletions examples/results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"javascript-candidate": {
"fibonacci": {
"ran": true,
"stdout": "909020757882815442762588444239 test passed\r\n909020757882815442762588444239 test passed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test passed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n909020757882815442762588444239 test failed\r\n",
"stderr": "",
"score": {
"passed": 3,
"failed": 18
}
},
"hello-world": {
"ran": true,
"stdout": "hello world\r\n",
"stderr": ""
}
},
"python-candidate": {
"test": {
"ran": true,
"stdout": "hello world\r\n",
"stderr": ""
}
},
"ruby-candidate": {
"test": {
"ran": true,
"stdout": "hello world\r\n",
"stderr": ""
}
}
}
16 changes: 16 additions & 0 deletions examples/templates/javascript/fibonacci/run.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<%= @solution %>

input = [
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
];
output = [
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765
];

for (var i=0; i<input.length; i++) {
if (fibonacci(input[i]) === output[i]) {
console.log('<%= @id %> test passed');
} else {
console.log('<%= @id %> test failed');
}
}
49 changes: 17 additions & 32 deletions lib/sandboxy.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
require 'rubygems'
require 'erb'
require 'json'

require 'commander'
require 'docker'
require 'colorize'
require 'active_support'
require 'active_support/core_ext'

require_relative 'sandboxy/ui'
require_relative 'sandboxy/patch'
require_relative 'sandboxy/commands'

module Sandboxy
class App
include Commander::Methods

PATH = File.expand_path('../../', __FILE__).freeze

def run
program :name, 'sandboxy'
program :version, '0.0.1'
program :description, 'Run a variety of programs sandboxed in containers'
PATH = File.expand_path('../../', __FILE__).freeze

Sandboxy::Commands.constants
.select { |c| Sandboxy::Commands.const_get(c).is_a? Class }
.each do |cmd|
def self.path(p)
File.expand_path(p, PATH)
end
end

command cmd.to_s.underscore.to_sym do |c|
command = "Sandboxy::Commands::#{cmd}".constantize
c.syntax = command::SYNTAX
c.summary = command::SUMMARY
c.description = command::DESCRIPTION
c.example 'description', 'command example'
c.option '--some-switch', 'Some switch that does something'
require_relative 'sandboxy/ui'
require_relative 'sandboxy/patch'

c.action do |args, options|
command.new(args, options).run
end
end
end
require_relative 'sandboxy/commands'
require_relative 'sandboxy/config'
require_relative 'sandboxy/container'
require_relative 'sandboxy/language'
require_relative 'sandboxy/path'
require_relative 'sandboxy/template'

run!
end
end
end
require_relative 'sandboxy/app'
31 changes: 31 additions & 0 deletions lib/sandboxy/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Sandboxy::App
include Commander::Methods

def run
program :name, 'sandboxy'
program :version, '0.0.1'
program :description, <<DESCRIPTION
Run a variety of programs sandboxed in containers
DESCRIPTION

Sandboxy::Commands.constants
.select { |c| Sandboxy::Commands.const_get(c).is_a? Class }
.each do |cmd|

command cmd.to_s.underscore.to_sym do |c|
command = "Sandboxy::Commands::#{cmd}".constantize
c.syntax = command::SYNTAX
c.summary = command::SUMMARY
c.description = command::DESCRIPTION
c.example 'description', 'command example'
c.option '--some-switch', 'Some switch that does something'

c.action do |args, options|
command.new(args, options).run
end
end
end

run!
end
end
2 changes: 1 addition & 1 deletion lib/sandboxy/commands/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(args, options)
def run
puts 'Building Images'.light_blue

dockerfiles = Dir[File.expand_path('images/*.dockerfile', Sandboxy::App::PATH)]
dockerfiles = Dir[File.expand_path('images/*.dockerfile', Sandboxy::PATH)]

progress dockerfiles do |path|
dockerfile = File.basename(path)
Expand Down
79 changes: 47 additions & 32 deletions lib/sandboxy/commands/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,60 @@ class Sandboxy::Commands::Run
def initialize(args, options)
end

def run_app(language, cmd, path)
# Create Image
image = Docker::Image.create(fromImage: "sandboxy:#{language}")
.insert_local('localPath' => path, 'outputPath' => '/app')
def run
results = Hash.new { |hash,key| hash[key] = {} }

# Create Container
container = Docker::Container.create(Image: image.id, Cmd: cmd, Tty: true)
container.start
container.wait(10)
progress Dir['code/**/*.*'] do |path|
id = path.split('/')[-2]
file, slug, ext = Sandboxy::Path.new(path).info

result = container.logs(stdout: true)
name = file.bold

# Cleanup
container.stop
container.delete(force: true)
image.remove(force: true)
# If the question has an associated ERB template, wrap the file in the
# template.
template_id, path = Sandboxy::Template.wrap(path)

return result
end
# Get the language and run the file in a container runtime.
stdout, stderr, status_code = Sandboxy::Language.get_class(ext).run(path)

def run
puts 'Running'.light_blue
# Write to the results object.
res = results[id][slug] = {
ran: status_code == 0,
stdout: stdout,
stderr: stderr
}

progress Dir['code/**/*.*'] do |path|
file = File.basename(path)
ext = File.extname(path)[1..-1]

print file.bold

File.open(path.gsub(file, 'stdout'), "w") do |f|
case ext
when 'js'
f.puts run_app('javascript', ['nodejs', "/app/#{file}"], path)
when 'py'
f.puts run_app('python', ['python', "/app/#{file}"], path)
when 'rb'
f.puts run_app('ruby', ['ruby', "/app/#{file}"], path)
end
name = name.red unless res[:ran]

# Find any test results.
passed = stdout.lines.select do |line|
line =~ /#{template_id} test passed/
end.count

failed = stdout.lines.select do |line|
line =~ /#{template_id} test failed/
end.count

total = passed + failed

# If we have a template id and test have been run then save the test
# results.
unless template_id.nil? or total == 0
res[:score] = { passed: passed, failed: failed }

perc = res[:score][:passed] / total.to_f
score = "#{res[:score][:passed]}/#{total}"

score = score.light_red if perc < 0.33
score = score.light_yellow if perc.between?(0.33, 0.66)
score = score.light_green if perc > 0.66
end

print "\033[2K\r #{id.blue.bold} #{name} #{score}\n"
end

File.open('results.json', 'w') do |f|
f.write(JSON.pretty_generate(results))
end
end
end
10 changes: 10 additions & 0 deletions lib/sandboxy/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'yaml'

module Sandboxy
module Config
module Containers
CREATE = YAML.load_file(Sandboxy.path('config/containers/create.yml'))
.symbolize_keys
end
end
end
10 changes: 10 additions & 0 deletions lib/sandboxy/container.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Container
#
# Owns logic for operating on a container

module Sandboxy
module Container
end
end

require_relative 'container/image'
27 changes: 27 additions & 0 deletions lib/sandboxy/container/image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Sandboxy::Container::Image
def self.run(language, cmd, path)
# Create Image
image = Docker::Image.create(fromImage: "sandboxy:#{language}")
.insert_local('localPath' => path, 'outputPath' => '/app')

# Create Container
container = Docker::Container.create(
Image: image.id,
Cmd: cmd,
Tty: true,
**Sandboxy::Config::Containers::CREATE
)
container.start
ran = container.wait(10)

stdout = container.logs(stdout: true)
stderr = container.logs(stderr: true)

# Cleanup
container.stop
container.delete(force: true)
image.remove(force: true)

return stdout, stderr, ran['StatusCode']
end
end
28 changes: 28 additions & 0 deletions lib/sandboxy/language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Language
#
# Each language class owns the language specific logic for a job.

module Sandboxy
module Language
BY_EXTENSION = {
js: 'javascript',
py: 'python',
rb: 'ruby'
}

def self.get_class(ext)
return Object.const_get(
"Sandboxy::Language::#{BY_EXTENSION[ext.to_sym].capitalize}"
)
end

def self.get(ext)
BY_EXTENSION[ext.to_sym]
end
end
end

require_relative 'language/base'
require_relative 'language/javascript'
require_relative 'language/python'
require_relative 'language/ruby'
11 changes: 11 additions & 0 deletions lib/sandboxy/language/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Sandboxy::Language::Base
def run(path)
file = File.basename(path)

return Sandboxy::Container::Image.run(
self.name.downcase.split('::').last,
[self::CMD, "/app/#{file}"],
path
)
end
end
5 changes: 5 additions & 0 deletions lib/sandboxy/language/javascript.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Sandboxy::Language::Javascript
extend Sandboxy::Language::Base

self::CMD = 'nodejs'.freeze
end
Loading

0 comments on commit 2a964c6

Please sign in to comment.