Skip to content

Commit 437e7ba

Browse files
authored
Allow benchmarks in YAML format (#268)
1 parent f480cae commit 437e7ba

File tree

6 files changed

+176
-25
lines changed

6 files changed

+176
-25
lines changed

app/controllers/user_scripts_controller.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ def create
1919
bench.validate!
2020
if bench.valid?
2121
bench.run
22+
links = bench.names.map do |name|
23+
"<a href=\"/ruby/ruby/commits?result_type=#{name}\">#{name}</a>"
24+
end.join(', ')
2225

23-
render plain: t('.index.successful_submit', path: "/ruby/ruby/commits?result_type=#{bench.name}")
26+
render plain: t('.index.successful_submit', links: links)
2427
else
2528
render plain: bench.errors.join("\n"), status: 422
2629
end

app/jobs/remote_server_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def perform(initiator_key, benchmark_group, options = {})
2727
Net::SSH.start(
2828
secrets.bare_metal_server_ip,
2929
secrets.bare_metal_server_user,
30-
password: secrets.bare_metal_server_password
30+
password: secrets.bare_metal_server_password.to_s
3131
) do |ssh|
3232

3333
send(benchmark_group, ssh, initiator_key, options)

app/services/user_bench.rb

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
1+
require 'net/http'
2+
13
class UserBench
2-
attr_reader :errors, :name, :url, :sha, :sha2, :commits
4+
attr_reader :errors, :url, :sha, :sha2, :commits, :names
35

46
def initialize(name, url, sha, sha2 = nil)
57
@name = name&.strip
68
@url = url&.strip
79
@sha = sha&.strip
810
@sha2 = sha2&.strip
11+
@names = []
912
@errors = []
1013
@commits = []
1114
@client = Octokit::Client.new(access_token: Rails.application.secrets.github_api_token)
1215
end
1316

1417
def validate!
15-
errors.push(err('missing_name')) if name.blank?
18+
errors.push(err('missing_name')) if @name.blank?
1619
errors.push(err('missing_url')) if url.blank?
1720
errors.push(err('missing_sha')) if sha.blank?
1821
return if !valid?
1922

20-
errors.push(err('name_already_taken')) if name_taken?
2123
errors.push(err('bad_url')) unless valid_url?
22-
errors.push(err('unallowed_characters', name: name)) unless valid_name?
24+
errors.push(err('unallowed_characters', name: @name)) unless valid_name?
25+
return if !valid?
26+
27+
if !yaml_script? && !ruby_script?
28+
errors.push(err('unkown_extension', url: url))
29+
return
30+
end
2331

32+
validate_yaml if yaml_script?
33+
validate_names
34+
validate_script if ruby_script?
2435
return if !valid?
2536

2637
validate_sha(sha)
@@ -40,31 +51,80 @@ def run
4051
url: 'https://github.com/tgxworld/',
4152
)
4253
)
43-
BenchmarkType.create!(
44-
category: name,
45-
script_url: url,
46-
from_user: true,
47-
repo: repo
48-
)
54+
@names.each do |ty|
55+
BenchmarkType.create!(
56+
category: ty,
57+
script_url: url,
58+
from_user: true,
59+
repo: repo
60+
)
61+
end
4962

50-
RunUserBench.perform_later(name, url, commits.last.commit.committer.date.iso8601, commits.first.sha)
63+
RunUserBench.perform_later(@name, url, commits.last.commit.committer.date.iso8601, commits.first.sha)
5164
end
5265

5366
private
5467

68+
def validate_names
69+
if yaml_script? && Hash === @parsed_yaml
70+
benchmark = @parsed_yaml['benchmark']
71+
if Array === benchmark
72+
benchmark.each { |hash| @names << hash['name'] }
73+
elsif Hash === benchmark
74+
benchmark.keys.each { |n| @names << n }
75+
elsif String === benchmark
76+
@names << benchmark
77+
end
78+
end
79+
80+
if yaml_script? && @names.size == 0
81+
errors.push(err('yaml_file_without_benchmarks'))
82+
return
83+
elsif ruby_script?
84+
@names << @name
85+
end
86+
87+
taken_names = BenchmarkType.where(category: @names).pluck(:category)
88+
if taken_names.size > 0
89+
errors.push(err('name_already_taken', names: taken_names.join(', ')))
90+
end
91+
end
92+
93+
def yaml_script?
94+
url.match?(/\.ya?ml$/)
95+
end
96+
97+
def ruby_script?
98+
url.end_with?('.rb')
99+
end
100+
101+
def validate_script
102+
content = Net::HTTP.get(URI.parse(url)).strip
103+
content.force_encoding(Encoding::UTF_8)
104+
path = File.join(Dir.tmpdir, SecureRandom.hex)
105+
File.open(path, 'wt') { |file| file.write(content) }
106+
if !system("ruby -c #{path} > /dev/null 2>&1")
107+
errors.push(err('invalid_ruby_code', lines: CGI.escapeHTML(content.split("\n").first(5).join("\n"))))
108+
end
109+
File.delete(path)
110+
end
111+
112+
def validate_yaml
113+
yaml = Net::HTTP.get(URI.parse(url))
114+
@parsed_yaml = YAML.safe_load(yaml)
115+
rescue Psych::SyntaxError, Psych::DisallowedClass
116+
errors.push(err('invalid_yaml'))
117+
end
118+
55119
def valid_url?
56120
uri = URI.parse(url)
57121
URI::HTTP === uri || URI::HTTPS === uri
58122
rescue URI::InvalidURIError
59123
false
60124
end
61125

62-
def name_taken?
63-
BenchmarkType.exists?(category: name)
64-
end
65-
66126
def valid_name?
67-
name.match?(/^[a-zA-Z0-9\-_]+$/)
127+
@name.match?(/^[a-zA-Z0-9\-_]+$/)
68128
end
69129

70130
def validate_sha(sha)

config/locales/en.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,22 @@ en:
2323
commit_sha: Commit SHA
2424
second_commit_sha: Second commit SHA
2525
submit: Submit
26-
successful_submit: "Success! Results will be published <a href=\"%{path}\">here</a>."
26+
successful_submit: "Success! Results will be published at %{links}."
2727
errors:
2828
missing_name: Missing Benchmark Name
2929
missing_url: Missing Script URL
3030
missing_sha: Missing Commit SHA
31-
name_already_taken: Benchmarks Name is already taken
31+
name_already_taken: "`%{names}` already exist"
3232
bad_url: Invalid Script URL
33+
unkown_extension: "Unknown file extension in the URL %{url}"
3334
unallowed_characters: "\"%{name}\" contains unallowed characters. Allowed characters are: a-z, A-Z, 0-9, hyphens and underscores"
3435
bad_sha: "Couldn't find Ruby commit for SHA %{sha}"
36+
invalid_yaml: Invalid YAML syntax
37+
invalid_ruby_code: |
38+
The ruby code you provided has syntax errors. The first 5 lines are:
39+
%{lines}
40+
41+
yaml_file_without_benchmarks: The provided YAML file doesn't contain any benchmarks
3542
organizations:
3643
index:
3744
title: Benchmarks

test/controllers/user_scripts_controller_test.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ class UserScriptsControllerTest < ActionDispatch::IntegrationTest
3333

3434
test 'trusted users can POST to #create' do
3535
sign_in trusted: true
36+
Net::HTTP.stubs(:get).returns(<<~RUBY)
37+
def xzczx
38+
puts 45
39+
end
40+
RUBY
3641
assert_enqueued_with(job: RunUserBench) do
3742
VCR.use_cassette('github ruby 6ffef8d459') do
38-
post '/user-scripts.json', params: { name: 'some_benchmark', url: 'http://script.com', sha: '6ffef8d459' }
43+
post '/user-scripts.json', params: { name: 'some_benchmark', url: 'http://script.com/script.rb', sha: '6ffef8d459' }
3944
end
4045
end
4146
assert_response :success
42-
assert_equal response.body, 'Success! Results will be published <a href="/ruby/ruby/commits?result_type=some_benchmark">here</a>.'
47+
assert_equal response.body, I18n.t('user_scripts.index.successful_submit', links: '<a href="/ruby/ruby/commits?result_type=some_benchmark">some_benchmark</a>')
4348
assert_equal BenchmarkType.where(category: 'some_benchmark', from_user: true).count, 1
4449
end
4550
end

test/services/user_bench_test.rb

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ def valid_name
1515
'valid_name'
1616
end
1717

18-
def valid_url
19-
'http://github.com'
18+
def valid_url(ext = 'rb')
19+
"https://raw.githubusercontent.com/ruby/ruby/master/benchmark/irb_color.#{ext}"
2020
end
2121

22+
def valid_ruby
23+
<<~RUBY
24+
def msdasd
25+
puts 54544
26+
end
27+
RUBY
28+
end
2229
test '#validate! @name presence, uniqueness and allowed characters' do
2330
bench = new_bench('')
2431
bench.validate!
@@ -30,9 +37,10 @@ def valid_url
3037
repo_id: 77
3138
)
3239

40+
Net::HTTP.stubs(:get).returns(valid_ruby)
3341
bench = new_bench('taken_name', valid_url, 'asdasd')
3442
bench.validate!
35-
assert_includes bench.errors, err('name_already_taken')
43+
assert_includes bench.errors, err('name_already_taken', names: 'taken_name')
3644

3745
bench = new_bench('/invalid name', valid_url, 'dads')
3846
bench.validate!
@@ -55,6 +63,7 @@ def valid_url
5563
assert_includes bench.errors, err('missing_sha')
5664

5765
bench = new_bench(valid_name, valid_url, 'doesntexist')
66+
Net::HTTP.stubs(:get).returns(valid_ruby)
5867
VCR.use_cassette('github ruby doesntexist') do
5968
bench.validate!
6069
end
@@ -63,6 +72,7 @@ def valid_url
6372

6473
test '#validate! when everything is valid' do
6574
bench = new_bench(valid_name, valid_url, '6ffef8d459')
75+
Net::HTTP.stubs(:get).returns(valid_ruby)
6676
VCR.use_cassette('github ruby 6ffef8d459') do
6777
bench.validate!
6878
end
@@ -73,6 +83,7 @@ def valid_url
7383

7484
test "#validate! @sha2 existence on github only if it's given" do
7585
bench = new_bench(valid_name, valid_url, '6ffef8d459', 'doesntexist')
86+
Net::HTTP.stubs(:get).returns(valid_ruby)
7687
VCR.use_cassette('github ruby 6ffef8d459') do
7788
VCR.use_cassette('github ruby doesntexist') do
7889
bench.validate!
@@ -83,6 +94,7 @@ def valid_url
8394

8495
test '#validate! allows 2 shas' do
8596
bench = new_bench(valid_name, valid_url, '6ffef8d459', 'fe0ddf0e58')
97+
Net::HTTP.stubs(:get).returns(valid_ruby)
8698
VCR.use_cassette('github ruby 6ffef8d459') do
8799
VCR.use_cassette('github ruby fe0ddf0e58') do
88100
bench.validate!
@@ -96,4 +108,68 @@ def valid_url
96108
second_commit_date = bench.commits.second.commit.committer.date
97109
assert first_commit_date < second_commit_date
98110
end
111+
112+
test '#validate! yaml file and benchmark names inside of it are validated' do
113+
yaml = <<~YAML
114+
prelude: |
115+
str1 = [*"a".."z",*"0".."9"].join("")
116+
str10 = str1 * 10
117+
str100 = str10 * 10
118+
str1000 = str100 * 10
119+
benchmark:
120+
upcase-1: str1.upcase
121+
upcase-10: str10.upcase
122+
upcase-100: str100.upcase
123+
upcase-1000: str1000.upcase
124+
YAML
125+
Net::HTTP.stubs(:get).returns(yaml)
126+
bench = new_bench(valid_name, valid_url('yml'), '6ffef8d459')
127+
VCR.use_cassette('github ruby 6ffef8d459') do
128+
bench.validate!
129+
end
130+
expected = %w{upcase-1 upcase-10 upcase-100 upcase-1000}
131+
assert bench.valid?
132+
assert_equal bench.names.size, expected.size
133+
expected.each { |e| assert_includes bench.names, e }
134+
end
135+
136+
test '#validate! yaml syntax and benchmark-driver format' do
137+
yaml = <<~YAML
138+
prelude: |
139+
str1 = [*"a".."z",*"0".."9"].join("")
140+
str10 = str1 * 10
141+
str100 = str10 * 10
142+
str1000 = str100 * 10
143+
YAML
144+
Net::HTTP.stubs(:get).returns(yaml)
145+
bench = new_bench(valid_name, valid_url('yml'), '6ffef8d459')
146+
VCR.use_cassette('github ruby 6ffef8d459') do
147+
bench.validate!
148+
end
149+
assert_not bench.valid?
150+
assert_includes bench.errors, err('yaml_file_without_benchmarks')
151+
152+
yaml = 'asddsf: weads:'
153+
Net::HTTP.stubs(:get).returns(yaml)
154+
bench = new_bench(valid_name, valid_url('yml'), '6ffef8d459')
155+
VCR.use_cassette('github ruby 6ffef8d459') do
156+
bench.validate!
157+
end
158+
assert_not bench.valid?
159+
assert_includes bench.errors, err('invalid_yaml')
160+
end
161+
162+
test '#validate! ruby code' do
163+
bad_ruby = <<~RUBY
164+
def txxsr(
165+
[1,2,].each do
166+
RUBY
167+
Net::HTTP.stubs(:get).returns(bad_ruby)
168+
bench = new_bench(valid_name, valid_url, '6ffef8d459')
169+
VCR.use_cassette('github ruby 6ffef8d459') do
170+
bench.validate!
171+
end
172+
assert_not bench.valid?
173+
assert_includes bench.errors.map(&:strip), err('invalid_ruby_code', lines: bad_ruby).strip
174+
end
99175
end

0 commit comments

Comments
 (0)