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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a8e623818974d8e1d0f091e5efa200ae14e80e9
4
- data.tar.gz: ddb33cfdb7c7de09781dad8413691c9394721807
3
+ metadata.gz: e574ef63ec53b93d7841532738042a3be0899079
4
+ data.tar.gz: a87d92c79f8af4546ef46eb34e6faa7e0d26572c
5
5
  SHA512:
6
- metadata.gz: fd7ece620802e0aa40b42bd7bb15a205748d0628bc5d27367838572c7669ef67dd3166ce31a6db5120df5a00d94f547ab4d0e9f329b24ccc6a2821762a06b4e3
7
- data.tar.gz: ddc60b701bfdc9c86b479cf518512302bca3c6cc21636ef4c902d03a0f84b6f5760f9bde9b40eca422f39da9ce351a103d39d2d01f290d91ef9001dd197a79a0
6
+ metadata.gz: 28dca88c0eae83a3d5e34173b47b6702d805e1bc3d3ff70a99d7d546001c157386f91ca9d6e8b93c13b43561576caf0b1d2e0e4329b598d79c73e5ab1f8ca4b4
7
+ data.tar.gz: aeba550111b462b085d48c9b590822ddd03fdefa874303ccadb13c89867c55120c06612ded7402f871fb3cdaffb95c3d2297e45cd4310c2af36a167ed4e66240
@@ -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 = "Dataset #{solution[:id]}"
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, 'Las columnas no coinciden')
15
+ failed(name, result, solution, (I18n.t 'failure.columns'))
16
16
  when :distinct_rows
17
- failed(name, result, solution, 'Las filas no coinciden')
17
+ failed(name, result, solution, (I18n.t 'failure.rows'))
18
18
  else
19
- failed(name, result, solution, 'Las consultas no coinciden')
19
+ failed(name, result, solution, (I18n.t 'failure.query'))
20
20
  end
21
21
  end
22
22
 
@@ -3,9 +3,12 @@ module Sqlite
3
3
  attr_reader :header, :rows
4
4
 
5
5
  def initialize(data)
6
- rows = data.split(/\n+/i)
7
- @header = rows.shift.split(/\|/)
8
- @rows = rows.map { |row| row.split(/\|/) }
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
@@ -0,0 +1,7 @@
1
+ module Sqlite
2
+ class TestSolutionTypeError < StandardError
3
+ def initialize(msg = 'Yaml Test must have solution_type with one of these values: query or datasets')
4
+ super
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class Hash
3
+ def to_yaml
4
+ super.sub('---', '').strip
5
+ end
6
+ end
@@ -11,19 +11,51 @@ module Sqlite
11
11
 
12
12
  def render_error(result, solution, error)
13
13
  @error = error
14
- @result = {
15
- header: result[:dataset].header,
16
- rows: result[:dataset].rows
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
@@ -1,6 +1,8 @@
1
1
  en:
2
- records: 'Records'
3
- memory: 'Memory'
4
- special_records: 'Special records'
5
- flags: 'Flags'
6
- check_equal_failure: "<b>%{record}</b> should be <b>%{expected}</b>, but was <b>%{actual}</b>"
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'
@@ -1,6 +1,8 @@
1
1
  es:
2
- records: 'Registros'
3
- memory: 'Memoria'
4
- special_records: 'Registros especiales'
5
- flags: 'Banderas'
6
- check_equal_failure: "<b>%{record}</b> debería contener el valor <b>%{expected}</b>, pero se encontró <b>%{actual}</b>"
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'
@@ -4,7 +4,7 @@ class SqliteMetadataHook < Mumukit::Hook
4
4
  language: {
5
5
  name: 'sqlite',
6
6
  icon: { type: 'devicon', name: 'sql' },
7
- version: 'v0.2.2',
7
+ version: '3.13.0',
8
8
  extension: 'sql',
9
9
  ace_mode: 'sql',
10
10
  graphic: true
@@ -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
@@ -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 = 'leandrojdl/mumuki-sqlite-worker'
9
- config.content_type = 'plain'
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'
@@ -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 adhoc program that receives .sql files.
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 .sql file template from request structure
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: [mulang verifications] todo: better explain
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, datasets = parse_test request.test.strip
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: 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 = post_process_datasets(output['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
- # Split query by '-- DATASET' line match
80
- # First match is teacher's solution
81
- # Rest are Datasets
82
- # Example:
83
- # select * from table
84
- # -- DATASET
85
- # insert into 1
86
- # -- dataset
87
- # insert into 2
88
- def parse_test(content)
89
- query = content.split(/\s*--\s*dataset\s*\n+/i)
90
- return query.shift, query
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
@@ -1,3 +1,3 @@
1
1
  module SqliteVersionHook
2
- VERSION = '1.0.1'
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: 3px solid #0B465D;
6
+ border: none;
7
7
  }
8
8
  table.sqlite_solution {
9
- border: 3px solid darkred;
9
+ border: none;
10
10
  }
11
11
  table.sqlite_error tr:nth-child(even) {
12
- background-color: #DDDDDD;
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
- <% @result[:header].each do |field| %>
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
- <% row.each do |field| %>
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
- <% @solution[:header].each do |field| %>
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
- <% row.each do |field| %>
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>
@@ -20,7 +20,7 @@
20
20
  }
21
21
  </style>
22
22
 
23
- <h5>Consulta correcta!</h5>
23
+ <h5><%= I18n.t 'success.query' %></h5>
24
24
  <table class="table table-bordered sqlite_success">
25
25
  <thead>
26
26
  <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: 1.0.1
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-06-22 00:00:00.000000000 Z
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.12
210
+ rubygems_version: 2.6.14
193
211
  signing_key:
194
212
  specification_version: 4
195
213
  summary: SQLite Runner for Mumuki