mumuki-sqlite-runner 1.0.1 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/checker.rb +4 -4
- data/lib/dataset.rb +14 -3
- data/lib/dataset_solution_parser.rb +34 -0
- data/lib/errors.rb +7 -0
- data/lib/extensions/hash_extension.rb +6 -0
- data/lib/html_renderer.rb +40 -8
- data/lib/locales/en.yml +7 -5
- data/lib/locales/es.yml +7 -5
- data/lib/metadata_hook.rb +1 -1
- data/lib/query_solution_parser.rb +25 -0
- data/lib/sqlite_runner.rb +6 -2
- data/lib/test_hook.rb +78 -23
- data/lib/version_hook.rb +2 -2
- data/lib/view/rows_error.html.erb +43 -11
- data/lib/view/rows_success.html.erb +1 -1
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e574ef63ec53b93d7841532738042a3be0899079
|
4
|
+
data.tar.gz: a87d92c79f8af4546ef46eb34e6faa7e0d26572c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28dca88c0eae83a3d5e34173b47b6702d805e1bc3d3ff70a99d7d546001c157386f91ca9d6e8b93c13b43561576caf0b1d2e0e4329b598d79c73e5ab1f8ca4b4
|
7
|
+
data.tar.gz: aeba550111b462b085d48c9b590822ddd03fdefa874303ccadb13c89867c55120c06612ded7402f871fb3cdaffb95c3d2297e45cd4310c2af36a167ed4e66240
|
data/lib/checker.rb
CHANGED
@@ -6,17 +6,17 @@ module Sqlite
|
|
6
6
|
# when distinct_columns: return failed & render error
|
7
7
|
# when distinct_rows: return failed & render error
|
8
8
|
def check(result, solution)
|
9
|
-
name =
|
9
|
+
name = I18n.t 'dataset', number: solution[:id]
|
10
10
|
|
11
11
|
case solution[:dataset].compare result[:dataset]
|
12
12
|
when :equals
|
13
13
|
success(name, result)
|
14
14
|
when :distinct_columns
|
15
|
-
failed(name, result, solution,
|
15
|
+
failed(name, result, solution, (I18n.t 'failure.columns'))
|
16
16
|
when :distinct_rows
|
17
|
-
failed(name, result, solution,
|
17
|
+
failed(name, result, solution, (I18n.t 'failure.rows'))
|
18
18
|
else
|
19
|
-
failed(name, result, solution,
|
19
|
+
failed(name, result, solution, (I18n.t 'failure.query'))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
data/lib/dataset.rb
CHANGED
@@ -3,9 +3,12 @@ module Sqlite
|
|
3
3
|
attr_reader :header, :rows
|
4
4
|
|
5
5
|
def initialize(data)
|
6
|
-
rows =
|
7
|
-
|
8
|
-
|
6
|
+
@header = @rows = []
|
7
|
+
rows = split_lines data
|
8
|
+
unless rows.empty?
|
9
|
+
@header = split_pipe rows.shift
|
10
|
+
@rows = rows.map { |item| split_pipe item }
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
14
|
def compare(other)
|
@@ -23,6 +26,14 @@ module Sqlite
|
|
23
26
|
|
24
27
|
protected
|
25
28
|
|
29
|
+
def split_lines(str)
|
30
|
+
str.split(/\n+/i)
|
31
|
+
end
|
32
|
+
|
33
|
+
def split_pipe(line)
|
34
|
+
line.split(/\|/)
|
35
|
+
end
|
36
|
+
|
26
37
|
def same_header(other_header)
|
27
38
|
@header.eql? other_header
|
28
39
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sqlite
|
2
|
+
class DatasetSolutionParser
|
3
|
+
|
4
|
+
# If solutions comes in an explicit datasets,
|
5
|
+
# it was stored in instance variable.
|
6
|
+
# Expected input:
|
7
|
+
# OpenStruct#{
|
8
|
+
# solution_type: 'datasets',
|
9
|
+
# examples: [
|
10
|
+
# {
|
11
|
+
# dataset: "INSERT INTO ...\nINSERT INTO ...",
|
12
|
+
# solution_dataset: "id|field\n1|row1..."
|
13
|
+
# }
|
14
|
+
# ]
|
15
|
+
# }
|
16
|
+
def parse_test(test)
|
17
|
+
data = []
|
18
|
+
@solutions = []
|
19
|
+
solution_query = '-- none'
|
20
|
+
|
21
|
+
test.examples.each do |item|
|
22
|
+
@solutions << item[:solution_dataset].scan(/(?!\|).+(?<!\|)/).join("\n")
|
23
|
+
data.append item[:data]
|
24
|
+
end
|
25
|
+
|
26
|
+
return solution_query, data
|
27
|
+
end
|
28
|
+
|
29
|
+
def choose(_solution)
|
30
|
+
@solutions
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/lib/errors.rb
ADDED
data/lib/html_renderer.rb
CHANGED
@@ -11,19 +11,51 @@ module Sqlite
|
|
11
11
|
|
12
12
|
def render_error(result, solution, error)
|
13
13
|
@error = error
|
14
|
-
@result =
|
15
|
-
|
16
|
-
|
17
|
-
}
|
18
|
-
@solution = {
|
19
|
-
header: solution[:dataset].header,
|
20
|
-
rows: solution[:dataset].rows
|
21
|
-
}
|
14
|
+
@result = parse_dataset(result[:dataset].header, result[:dataset].rows)
|
15
|
+
@solution = parse_dataset(solution[:dataset].header, solution[:dataset].rows)
|
16
|
+
|
22
17
|
template_file_error.result binding
|
23
18
|
end
|
24
19
|
|
25
20
|
protected
|
26
21
|
|
22
|
+
def parse_dataset(header, rows)
|
23
|
+
header_sign = header.shift
|
24
|
+
rows = rows.map do |row|
|
25
|
+
{
|
26
|
+
sign: row.shift,
|
27
|
+
row: row
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
{
|
32
|
+
header: {
|
33
|
+
sign: header_sign,
|
34
|
+
class: diff_class_of(header_sign),
|
35
|
+
fields: header
|
36
|
+
},
|
37
|
+
rows: rows.map do |row|
|
38
|
+
{
|
39
|
+
sign:row[:sign],
|
40
|
+
class: diff_class_of(row[:sign]),
|
41
|
+
fields: row[:row]
|
42
|
+
}
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def diff_class_of(value)
|
49
|
+
case value
|
50
|
+
when '+'
|
51
|
+
'required'
|
52
|
+
when '-'
|
53
|
+
'errored'
|
54
|
+
else
|
55
|
+
'nothing'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
27
59
|
def template_file_success
|
28
60
|
ERB.new File.read("#{__dir__}/view/rows_success.html.erb")
|
29
61
|
end
|
data/lib/locales/en.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
dataset: "Dataset %{number}"
|
3
|
+
success:
|
4
|
+
query: 'Correct Query!'
|
5
|
+
failure:
|
6
|
+
columns: 'Columns do not match'
|
7
|
+
rows: 'Rows do not match'
|
8
|
+
query: 'Queries do not match'
|
data/lib/locales/es.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
es:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
dataset: "Set de datos %{number}"
|
3
|
+
success:
|
4
|
+
query: '¡Consulta correcta!'
|
5
|
+
failure:
|
6
|
+
columns: 'Las columnas no coinciden'
|
7
|
+
rows: 'Las filas no coinciden'
|
8
|
+
query: 'Las consultas no coinciden'
|
data/lib/metadata_hook.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sqlite
|
2
|
+
class QuerySolutionParser
|
3
|
+
|
4
|
+
# Expected input:
|
5
|
+
# OpenStruct#{
|
6
|
+
# solution_type: 'query',
|
7
|
+
# solution_query: 'select * from motores;',
|
8
|
+
# examples: [
|
9
|
+
# { dataset: "INSERT INTO ...\nINSERT INTO ..." }
|
10
|
+
# ]
|
11
|
+
# }
|
12
|
+
def parse_test(test)
|
13
|
+
data = test.examples.map do |item|
|
14
|
+
item[:data]
|
15
|
+
end
|
16
|
+
|
17
|
+
return test.solution_query, data
|
18
|
+
end
|
19
|
+
|
20
|
+
def choose(solution)
|
21
|
+
solution
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/sqlite_runner.rb
CHANGED
@@ -5,11 +5,12 @@ I18n.load_translations_path File.join(__dir__, 'locales', '*.yml')
|
|
5
5
|
|
6
6
|
Mumukit.runner_name = 'sqlite'
|
7
7
|
Mumukit.configure do |config|
|
8
|
-
config.docker_image = '
|
9
|
-
config.content_type = '
|
8
|
+
config.docker_image = 'mumuki/mumuki-sqlite-worker'
|
9
|
+
config.content_type = 'html'
|
10
10
|
config.structured = true
|
11
11
|
end
|
12
12
|
|
13
|
+
require_relative './extensions/hash_extension'
|
13
14
|
require_relative './version_hook'
|
14
15
|
require_relative './test_hook'
|
15
16
|
require_relative './metadata_hook'
|
@@ -17,3 +18,6 @@ require_relative './checker'
|
|
17
18
|
require_relative './multiple_executions_runner'
|
18
19
|
require_relative './html_renderer'
|
19
20
|
require_relative './dataset'
|
21
|
+
require_relative './dataset_solution_parser'
|
22
|
+
require_relative './query_solution_parser'
|
23
|
+
require_relative './errors'
|
data/lib/test_hook.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'diffy'
|
2
3
|
|
3
4
|
##
|
4
|
-
# This Hook allow to run Sqlite Worker from an
|
5
|
+
# This Hook allow to run Sqlite Worker from an ad-hoc program that receives .json files.
|
5
6
|
|
6
7
|
class SqliteTestHook < Mumukit::Templates::FileHook
|
7
8
|
|
@@ -18,22 +19,22 @@ class SqliteTestHook < Mumukit::Templates::FileHook
|
|
18
19
|
"runsql #{filename}"
|
19
20
|
end
|
20
21
|
|
21
|
-
# Define the .
|
22
|
-
# request = {
|
23
|
-
# test: (string) teacher's code that define which testing verification student code should pass,
|
24
|
-
# extra: (string) teacher's code that prepare field where student code should run,
|
25
|
-
# content: (string) student code,
|
26
|
-
# expectations: [
|
22
|
+
# Define the .json file template from request structure
|
23
|
+
# Input: request = {
|
24
|
+
# test: (yaml string) teacher's code that define which testing verification student code should pass,
|
25
|
+
# extra: (sql string) teacher's code that prepare field where student code should run,
|
26
|
+
# content: (sql string) student code,
|
27
|
+
# expectations: [] not using for now
|
27
28
|
# }
|
28
29
|
#
|
29
30
|
def compile_file_content(request)
|
30
|
-
solution,
|
31
|
+
solution, data = parse_test request.test
|
31
32
|
|
32
33
|
content = {
|
33
34
|
init: request.extra.strip,
|
34
35
|
solution: solution,
|
35
36
|
student: request.content.strip,
|
36
|
-
datasets:
|
37
|
+
datasets: data
|
37
38
|
}
|
38
39
|
|
39
40
|
content.to_json
|
@@ -56,8 +57,7 @@ class SqliteTestHook < Mumukit::Templates::FileHook
|
|
56
57
|
|
57
58
|
case status
|
58
59
|
when :passed
|
59
|
-
results
|
60
|
-
solutions = post_process_datasets(output['solutions'])
|
60
|
+
solutions, results = parse_output output
|
61
61
|
framework.test solutions, results
|
62
62
|
when :failed
|
63
63
|
[output['output'], status]
|
@@ -66,6 +66,42 @@ class SqliteTestHook < Mumukit::Templates::FileHook
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
protected
|
70
|
+
|
71
|
+
def parse_output(output)
|
72
|
+
results = output['results']
|
73
|
+
solutions = output['solutions']
|
74
|
+
unless @solution_parser.nil?
|
75
|
+
solutions = @solution_parser.choose solutions
|
76
|
+
end
|
77
|
+
|
78
|
+
diff(solutions, results)
|
79
|
+
end
|
80
|
+
|
81
|
+
def diff(solutions, results)
|
82
|
+
zipped = solutions.zip(results).map do |solution, result|
|
83
|
+
|
84
|
+
diff = Diffy::SplitDiff.new result << "\n", solution << "\n"
|
85
|
+
|
86
|
+
if diff.left.blank?
|
87
|
+
[solution, result]
|
88
|
+
else
|
89
|
+
res = post_process_diff diff.left
|
90
|
+
sol = post_process_diff diff.right
|
91
|
+
[sol, res]
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
zipped.transpose.map { |dataset| post_process_datasets dataset }
|
97
|
+
end
|
98
|
+
|
99
|
+
def post_process_diff(data)
|
100
|
+
data.scan(/^(\s|-|\+)(.+)/)
|
101
|
+
.map { |mark, content| mark << '|' << content }
|
102
|
+
.join("\n")
|
103
|
+
end
|
104
|
+
|
69
105
|
# Transforms array datasets into hash with :id & :rows
|
70
106
|
def post_process_datasets(datasets)
|
71
107
|
datasets.map.with_index do |dataset, i|
|
@@ -76,18 +112,37 @@ class SqliteTestHook < Mumukit::Templates::FileHook
|
|
76
112
|
end
|
77
113
|
end
|
78
114
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
115
|
+
# Test should have one of these formats:
|
116
|
+
#
|
117
|
+
# Solution by query:
|
118
|
+
# { solution_type: 'query',
|
119
|
+
# solution_query: 'SELECT * FROM ...',
|
120
|
+
# examples: [
|
121
|
+
# { data: "INSERT INTO..." }
|
122
|
+
# ]
|
123
|
+
# }
|
124
|
+
#
|
125
|
+
# Solution by datasets:
|
126
|
+
# { solution_type: 'datasets',
|
127
|
+
# examples: [
|
128
|
+
# { data: "INSERT INTO...",
|
129
|
+
# solution: "id|field\n1|row1..."
|
130
|
+
# }
|
131
|
+
# ]
|
132
|
+
# }
|
133
|
+
def parse_test(test)
|
134
|
+
test = OpenStruct.new YAML.load(test).deep_symbolize_keys
|
135
|
+
|
136
|
+
case test.solution_type.to_sym
|
137
|
+
when :query
|
138
|
+
@solution_parser = Sqlite::QuerySolutionParser.new
|
139
|
+
when :datasets
|
140
|
+
@solution_parser = Sqlite::DatasetSolutionParser.new
|
141
|
+
else
|
142
|
+
raise Sqlite::TestSolutionTypeError
|
143
|
+
end
|
144
|
+
|
145
|
+
@solution_parser.parse_test test
|
91
146
|
end
|
92
147
|
|
93
148
|
# Initialize Metatest Framework with Checker & Runner
|
data/lib/version_hook.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module SqliteVersionHook
|
2
|
-
VERSION = '
|
3
|
-
end
|
2
|
+
VERSION = '2.2.1'
|
3
|
+
end
|
@@ -3,13 +3,13 @@
|
|
3
3
|
width: auto;
|
4
4
|
}
|
5
5
|
table.sqlite_result {
|
6
|
-
border:
|
6
|
+
border: none;
|
7
7
|
}
|
8
8
|
table.sqlite_solution {
|
9
|
-
border:
|
9
|
+
border: none;
|
10
10
|
}
|
11
11
|
table.sqlite_error tr:nth-child(even) {
|
12
|
-
background-color: #
|
12
|
+
background-color: #EEEEEE;
|
13
13
|
}
|
14
14
|
table.sqlite_error tr:nth-child(odd) {
|
15
15
|
background-color: #FFFFFF;
|
@@ -23,6 +23,34 @@
|
|
23
23
|
width: auto;
|
24
24
|
text-align: left;
|
25
25
|
}
|
26
|
+
|
27
|
+
.required {
|
28
|
+
color: #5cb85c;
|
29
|
+
font-style: italic;
|
30
|
+
}
|
31
|
+
.errored {
|
32
|
+
color: #d9534f;
|
33
|
+
font-style: italic;
|
34
|
+
}
|
35
|
+
|
36
|
+
.required :first-child,
|
37
|
+
.errored :first-child,
|
38
|
+
.nothing :first-child {
|
39
|
+
font-style: normal;
|
40
|
+
padding-left: 2px;
|
41
|
+
padding-right: 2px;
|
42
|
+
background-color: white;
|
43
|
+
border-left: none;
|
44
|
+
border-bottom: 1px solid white;
|
45
|
+
}
|
46
|
+
.required :first-child {
|
47
|
+
color: white;
|
48
|
+
background-color: #5cb85c;
|
49
|
+
}
|
50
|
+
.errored :first-child {
|
51
|
+
color: white;
|
52
|
+
background-color: #d9534f;
|
53
|
+
}
|
26
54
|
</style>
|
27
55
|
|
28
56
|
<h5><%= @error %></h5>
|
@@ -34,8 +62,9 @@
|
|
34
62
|
<h6>Se esperaba:</h6>
|
35
63
|
<table class="table table-bordered sqlite_error sqlite_result">
|
36
64
|
<thead>
|
37
|
-
<tr>
|
38
|
-
|
65
|
+
<tr class="<%= @result[:header][:class] %>">
|
66
|
+
<th><%= @result[:header][:sign] %></th>
|
67
|
+
<% @result[:header][:fields].each do |field| %>
|
39
68
|
<th><%= field %></th>
|
40
69
|
<% end %>
|
41
70
|
</tr>
|
@@ -43,8 +72,9 @@
|
|
43
72
|
|
44
73
|
<tbody>
|
45
74
|
<% @result[:rows].each do |row| %>
|
46
|
-
<tr>
|
47
|
-
|
75
|
+
<tr class="<%= row[:class] %>">
|
76
|
+
<td><%= row[:sign] %></td>
|
77
|
+
<% row[:fields].each do |field| %>
|
48
78
|
<td><%= field %></td>
|
49
79
|
<% end %>
|
50
80
|
</tr>
|
@@ -58,8 +88,9 @@
|
|
58
88
|
<h6>Se obtuvo:</h6>
|
59
89
|
<table class="table table-bordered sqlite_error sqlite_solution">
|
60
90
|
<thead>
|
61
|
-
<tr>
|
62
|
-
|
91
|
+
<tr class="<%= @result[:header][:class] %>">
|
92
|
+
<th><%= @result[:header][:sign] %></th>
|
93
|
+
<% @solution[:header][:fields].each do |field| %>
|
63
94
|
<th><%= field %></th>
|
64
95
|
<% end %>
|
65
96
|
</tr>
|
@@ -67,8 +98,9 @@
|
|
67
98
|
|
68
99
|
<tbody>
|
69
100
|
<% @solution[:rows].each do |row| %>
|
70
|
-
<tr>
|
71
|
-
|
101
|
+
<tr class="<%= row[:class] %>">
|
102
|
+
<td><%= row[:sign] %></td>
|
103
|
+
<% row[:fields].each do |field| %>
|
72
104
|
<td><%= field %></td>
|
73
105
|
<% end %>
|
74
106
|
</tr>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mumuki-sqlite-runner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leandro Di Lorenzo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mumukit
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: diffy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -159,11 +173,15 @@ extra_rdoc_files: []
|
|
159
173
|
files:
|
160
174
|
- lib/checker.rb
|
161
175
|
- lib/dataset.rb
|
176
|
+
- lib/dataset_solution_parser.rb
|
177
|
+
- lib/errors.rb
|
178
|
+
- lib/extensions/hash_extension.rb
|
162
179
|
- lib/html_renderer.rb
|
163
180
|
- lib/locales/en.yml
|
164
181
|
- lib/locales/es.yml
|
165
182
|
- lib/metadata_hook.rb
|
166
183
|
- lib/multiple_executions_runner.rb
|
184
|
+
- lib/query_solution_parser.rb
|
167
185
|
- lib/sqlite_runner.rb
|
168
186
|
- lib/test_hook.rb
|
169
187
|
- lib/version_hook.rb
|
@@ -189,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
207
|
version: '0'
|
190
208
|
requirements: []
|
191
209
|
rubyforge_project:
|
192
|
-
rubygems_version: 2.6.
|
210
|
+
rubygems_version: 2.6.14
|
193
211
|
signing_key:
|
194
212
|
specification_version: 4
|
195
213
|
summary: SQLite Runner for Mumuki
|