mumuki-sqlite-runner 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d075a041f2a6a4d3e8ee0c156fb9533443625287
4
+ data.tar.gz: 6d7e335083a7442e80ce5e83ecda14d1e214fa26
5
+ SHA512:
6
+ metadata.gz: 7e8082c65194aba7d81a10db77aac0884cb1773d0604a7eb0e83b87a10a147e284568677fde04cab048ffcaa7485137981af5507e4c15dc6acd73b8428c3f8ab
7
+ data.tar.gz: 57cf7a92f1805f7df7cf2c733c7c8e42e8d065cc66b58ad3947c17497874b6ebe054a55ccf23cada3b79d554d83e8d88d45a20cd3c6c3984c557f6910a28a479
data/lib/checker.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Sqlite
2
+ class Checker < Mumukit::Metatest::Checker
3
+
4
+ # Verify if solution dataset is equals than result
5
+ # when equals: Return passed & render success
6
+ # when distinct_columns: return failed & render error
7
+ # when distinct_rows: return failed & render error
8
+ def check(result, solution)
9
+ name = "Dataset #{solution[:id]}"
10
+
11
+ case solution[:dataset].compare result[:dataset]
12
+ when :equals
13
+ success(name, result)
14
+ when :distinct_columns
15
+ failed(name, result, solution, 'Las columnas no coinciden')
16
+ when :distinct_rows
17
+ failed(name, result, solution, 'Las filas no coinciden')
18
+ else
19
+ failed(name, result, solution, 'Las consultas no coinciden')
20
+ end
21
+ end
22
+
23
+ def success(name, result)
24
+ [name, :passed, render_success(result)]
25
+ end
26
+
27
+ def failed(name, result, solution, error)
28
+ [name, :failed, render_error(result, solution, error)]
29
+ end
30
+
31
+ # Return success page rendered with results
32
+ def render_success(result)
33
+ renderer.render_success result
34
+ end
35
+
36
+ # Return error page rendered with results & solutions
37
+ def render_error(result, solution, error)
38
+ renderer.render_error result, solution, error
39
+ end
40
+
41
+ private
42
+
43
+ def renderer
44
+ @renderer ||= Sqlite::HtmlRenderer.new
45
+ end
46
+ end
47
+ end
data/lib/dataset.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Sqlite
2
+ class Dataset
3
+ attr_reader :header, :rows
4
+
5
+ def initialize(data)
6
+ rows = data.split(/\n+/i)
7
+ @header = rows.shift.split(/\|/)
8
+ @rows = rows.map { |row| row.split(/\|/) }
9
+ end
10
+
11
+ def compare(other)
12
+ same_header = same_header other.header
13
+ same_rows = same_rows other.rows
14
+
15
+ if same_header & same_rows
16
+ :equals
17
+ elsif !same_header
18
+ :distinct_columns
19
+ else
20
+ :distinct_rows
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def same_header(other_header)
27
+ @header.eql? other_header
28
+ end
29
+
30
+ def same_rows(other_rows)
31
+ same_amount(other_rows) && same_content(other_rows)
32
+ end
33
+
34
+ def same_amount(other_rows)
35
+ @rows.length == other_rows.length
36
+ end
37
+
38
+ def same_content(other_rows)
39
+ @rows.zip(other_rows).all? { |tuple| tuple[0] == tuple[1] }
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Sqlite
4
+ class HtmlRenderer
5
+
6
+ def render_success(result)
7
+ @header = result[:dataset].header
8
+ @rows = result[:dataset].rows
9
+ template_file_success.result binding
10
+ end
11
+
12
+ def render_error(result, solution, error)
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
+ }
22
+ template_file_error.result binding
23
+ end
24
+
25
+ protected
26
+
27
+ def template_file_success
28
+ ERB.new File.read("#{__dir__}/view/rows_success.html.erb")
29
+ end
30
+
31
+ def template_file_error
32
+ ERB.new File.read("#{__dir__}/view/rows_error.html.erb")
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,6 @@
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>"
@@ -0,0 +1,6 @@
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>"
@@ -0,0 +1,18 @@
1
+ class SqliteMetadataHook < Mumukit::Hook
2
+ def metadata
3
+ {
4
+ language: {
5
+ name: 'sqlite',
6
+ icon: { type: 'devicon', name: 'sqlite' },
7
+ version: 'v0.2.2',
8
+ extension: 'sql',
9
+ ace_mode: 'assembly_x86',
10
+ graphic: true
11
+ },
12
+ test_framework: {
13
+ name: 'metatest',
14
+ test_extension: 'yml'
15
+ }
16
+ }
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Sqlite
2
+ class MultipleExecutionsRunner
3
+ def run(output, example)
4
+ result = output.find { |it| it[:id] == example[:id] }
5
+
6
+ raise Mumukit::Metatest::Errored, result[:error] if result[:error]
7
+ result
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require 'mumukit'
2
+ require 'erb'
3
+
4
+ I18n.load_translations_path File.join(__dir__, 'locales', '*.yml')
5
+
6
+ Mumukit.runner_name = 'sqlite'
7
+ Mumukit.configure do |config|
8
+ config.docker_image = 'leandrojdl/mumuki-sqlite-worker'
9
+ config.content_type = 'plain'
10
+ config.structured = true
11
+ end
12
+
13
+ require_relative './test_hook'
14
+ require_relative './metadata_hook'
15
+ require_relative './checker'
16
+ require_relative './multiple_executions_runner'
17
+ require_relative './html_renderer'
18
+ require_relative './dataset'
data/lib/test_hook.rb ADDED
@@ -0,0 +1,99 @@
1
+ require 'json'
2
+
3
+ ##
4
+ # This Hook allow to run Sqlite Worker from an adhoc program that receives .sql files.
5
+
6
+ class SqliteTestHook < Mumukit::Templates::FileHook
7
+
8
+ # Define that worker runs on a freshly-cleaned environment
9
+ isolated
10
+
11
+ # Just define file extension
12
+ def tempfile_extension
13
+ '.json'
14
+ end
15
+
16
+ # Define the command to be run by sqlite worker
17
+ def command_line(filename)
18
+ "runsql #{filename}"
19
+ end
20
+
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
27
+ # }
28
+ #
29
+ def compile_file_content(request)
30
+ solution, datasets = parse_test request.test.strip
31
+
32
+ content = {
33
+ init: request.extra.strip,
34
+ solution: solution,
35
+ student: request.content.strip,
36
+ datasets: datasets
37
+ }
38
+
39
+ content.to_json
40
+ end
41
+
42
+ # Define how output results
43
+ # Expected:
44
+ # {
45
+ # "solutions": [
46
+ # "name\nTest 1.1\nTest 1.2\nTest 1.3\n",
47
+ # "name\nTest 2.1\nTest 2.2\nTest 2.3\n"
48
+ # ],
49
+ # "results": [
50
+ # "id|name\n1|Test 1.1\n2|Test 1.2\n3|Test 1.3\n",
51
+ # "id|name\n1|Test 2.1\n2|Test 2.2\n3|Test 2.3\n"
52
+ # ]
53
+ # }
54
+ def post_process_file(_file, result, status)
55
+ output = JSON.parse(result)
56
+
57
+ case status
58
+ when :passed
59
+ results = post_process_datasets(output['results'])
60
+ solutions = post_process_datasets(output['solutions'])
61
+ framework.test solutions, results
62
+ when :failed
63
+ [output['output'], status]
64
+ else
65
+ [output, status]
66
+ end
67
+ end
68
+
69
+ # Transforms array datasets into hash with :id & :rows
70
+ def post_process_datasets(datasets)
71
+ datasets.map.with_index do |dataset, i|
72
+ {
73
+ id: i + 1,
74
+ dataset: Sqlite::Dataset.new(dataset)
75
+ }
76
+ end
77
+ end
78
+
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
91
+ end
92
+
93
+ # Initialize Metatest Framework with Checker & Runner
94
+ def framework
95
+ Mumukit::Metatest::Framework.new checker: Sqlite::Checker.new,
96
+ runner: Sqlite::MultipleExecutionsRunner.new
97
+ end
98
+
99
+ end
@@ -0,0 +1,81 @@
1
+ <style>
2
+ table.sqlite_error {
3
+ width: auto;
4
+ }
5
+ table.sqlite_result {
6
+ border: 3px solid #0B465D;
7
+ }
8
+ table.sqlite_solution {
9
+ border: 3px solid darkred;
10
+ }
11
+ table.sqlite_error tr:nth-child(even) {
12
+ background-color: #DDDDDD;
13
+ }
14
+ table.sqlite_error tr:nth-child(odd) {
15
+ background-color: #FFFFFF;
16
+ }
17
+ table.sqlite_error th {
18
+ color: white;
19
+ font-weight: bold;
20
+ background-color: #2E2F30;
21
+ }
22
+ table.sqlite_error td {
23
+ width: auto;
24
+ text-align: left;
25
+ }
26
+ </style>
27
+
28
+ <h5><%= @error %></h5>
29
+
30
+ <div class="row">
31
+
32
+ <!-- Result -->
33
+ <div class="col-md-6">
34
+ <h6>Se esperaba:</h6>
35
+ <table class="table table-bordered sqlite_error sqlite_result">
36
+ <thead>
37
+ <tr>
38
+ <% @result[:header].each do |field| %>
39
+ <th><%= field %></th>
40
+ <% end %>
41
+ </tr>
42
+ </thead>
43
+
44
+ <tbody>
45
+ <% @result[:rows].each do |row| %>
46
+ <tr>
47
+ <% row.each do |field| %>
48
+ <td><%= field %></td>
49
+ <% end %>
50
+ </tr>
51
+ <% end %>
52
+ </tbody>
53
+ </table>
54
+ </div>
55
+
56
+ <!-- Solution -->
57
+ <div class="col-md-6">
58
+ <h6>Se obtuvo:</h6>
59
+ <table class="table table-bordered sqlite_error sqlite_solution">
60
+ <thead>
61
+ <tr>
62
+ <% @solution[:header].each do |field| %>
63
+ <th><%= field %></th>
64
+ <% end %>
65
+ </tr>
66
+ </thead>
67
+
68
+ <tbody>
69
+ <% @solution[:rows].each do |row| %>
70
+ <tr>
71
+ <% row.each do |field| %>
72
+ <td><%= field %></td>
73
+ <% end %>
74
+ </tr>
75
+ <% end %>
76
+ </tbody>
77
+ </table>
78
+ </div>
79
+ </div>
80
+
81
+
@@ -0,0 +1,42 @@
1
+ <style>
2
+ table.sqlite_success {
3
+ width: auto;
4
+ border: none;
5
+ }
6
+ table.sqlite_success tr:nth-child(even) {
7
+ background-color: #DDDDDD;
8
+ }
9
+ table.sqlite_success tr:nth-child(odd) {
10
+ background-color: #FFFFFF;
11
+ }
12
+ table.sqlite_success th {
13
+ color: white;
14
+ font-weight: bold;
15
+ background-color: #BBBBBB;
16
+ }
17
+ table.sqlite_success td {
18
+ width: auto;
19
+ text-align: left;
20
+ }
21
+ </style>
22
+
23
+ <h5>Consulta correcta!</h5>
24
+ <table class="table table-bordered sqlite_success">
25
+ <thead>
26
+ <tr>
27
+ <% @header.each do |field| %>
28
+ <th><%= field %></th>
29
+ <% end %>
30
+ </tr>
31
+ </thead>
32
+
33
+ <tbody>
34
+ <% @rows.each do |row| %>
35
+ <tr>
36
+ <% row.each do |field| %>
37
+ <td><%= field %></td>
38
+ <% end %>
39
+ </tr>
40
+ <% end %>
41
+ </tbody>
42
+ </table>
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mumuki-sqlite-runner
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Di Lorenzo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mumukit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rouge
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: codeclimate-test-reporter
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mumukit-bridge
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.3'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.3'
153
+ description:
154
+ email:
155
+ - leandro.jdl@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - lib/checker.rb
161
+ - lib/dataset.rb
162
+ - lib/html_renderer.rb
163
+ - lib/locales/en.yml
164
+ - lib/locales/es.yml
165
+ - lib/metadata_hook.rb
166
+ - lib/multiple_executions_runner.rb
167
+ - lib/sqlite_runner.rb
168
+ - lib/test_hook.rb
169
+ - lib/view/rows_error.html.erb
170
+ - lib/view/rows_success.html.erb
171
+ homepage: http://github.com/leandrojdl/mumuki-sqlite-runner
172
+ licenses:
173
+ - MIT
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.5.1
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: SQLite Runner for Mumuki
195
+ test_files: []