mumuki-gobstones-runner 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/assets/boom.png +0 -0
- data/lib/expectations_hook.rb +102 -0
- data/lib/gobstones_runner.rb +13 -0
- data/lib/metadata_hook.rb +16 -0
- data/lib/stones_spec.rb +21 -0
- data/lib/stones_spec/error_message_parser.rb +17 -0
- data/lib/stones_spec/example.rb +47 -0
- data/lib/stones_spec/gobstones.rb +55 -0
- data/lib/stones_spec/hash.rb +5 -0
- data/lib/stones_spec/html_board_renderer.rb +77 -0
- data/lib/stones_spec/postcondition/expected_boom.rb +55 -0
- data/lib/stones_spec/postcondition/expected_final_board.rb +56 -0
- data/lib/stones_spec/postcondition/expected_return_value.rb +44 -0
- data/lib/stones_spec/postcondition/postcondition.rb +35 -0
- data/lib/stones_spec/precondition.rb +26 -0
- data/lib/stones_spec/runner.rb +41 -0
- data/lib/stones_spec/string.rb +10 -0
- data/lib/stones_spec/subject.rb +80 -0
- data/lib/stones_spec/version.rb +3 -0
- data/lib/stones_spec/with_command_line.rb +7 -0
- data/lib/stones_spec/with_gbb_html_rendering.rb +60 -0
- data/lib/stones_spec/with_gobstones_css.rb +112 -0
- data/lib/stones_spec/with_tempfile.rb +13 -0
- data/lib/test_hook.rb +24 -0
- data/lib/with_test_parser.rb +8 -0
- metadata +153 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZjI0MTQwYWIzZTQ4OThjZjc5NGFhODY3Y2ZiZGVhYzg0YTFjZWEzNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGIwMmMyMzc2NWQ0OGM3NWIzYmYzZDEwODAyYjI4MjA2YjMzMjA3Nw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDk4Njk0YjVkM2JjMDhjMDgxNGE2MTU5NDBlMjNmZGMxZDdiOTRiOTI2NmJm
|
10
|
+
NjYwYzU2MjY3NTFlZTQzNWM2ZjI1ZjVjZDNlMmM3MTYyZWJiYTc1OWNlNzMw
|
11
|
+
ZTUxMjYyNTgyZDJiN2I3NGY5MTQyOThkOTg5NjQxMmZlMDhlM2Y=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OTgwMzNhMWIwNTU4NDk1NWQ1OWM2ZjdkYWY2N2ZmMzkxY2E4MGRmNmQ2Mzlh
|
14
|
+
Y2QyNjcxYWQzNWMwMWU3MTIxNzFkMDBjNTk1M2NkM2VjNjA4YmMyYTZlZWIx
|
15
|
+
ZDc3MmRmNmMwZmYxNzM3YjY5N2IxNWE5NjYzYjE5Mzk5NTViYmE=
|
data/lib/assets/boom.png
ADDED
Binary file
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mumukit'
|
2
|
+
require 'mumukit/inspection'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require_relative 'with_test_parser'
|
6
|
+
|
7
|
+
module EvalExpectationsOnAST
|
8
|
+
def eval_in_gobstones(binding, ast)
|
9
|
+
pattern_generator = expectations[type]
|
10
|
+
|
11
|
+
if pattern_generator
|
12
|
+
!!(ast =~ pattern_generator[binding])
|
13
|
+
else
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def use(regexp)
|
19
|
+
lambda { |_| regexp }
|
20
|
+
end
|
21
|
+
|
22
|
+
def subject_for(binding)
|
23
|
+
if binding == 'program'
|
24
|
+
StonesSpec::Subject::Program
|
25
|
+
else
|
26
|
+
StonesSpec::Subject.from(binding)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_repeat_of(target)
|
31
|
+
use(/AST\(repeat\s*#{target}/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Mumukit::Inspection::PlainInspection
|
36
|
+
include EvalExpectationsOnAST
|
37
|
+
|
38
|
+
def expectations
|
39
|
+
{
|
40
|
+
'HasBinding' => lambda { |binding| subject_for(binding).ast_regexp },
|
41
|
+
'HasForeach' => use(/AST\(foreach/),
|
42
|
+
'HasRepeat' => check_repeat_of('.+'),
|
43
|
+
'HasVariable' => use(/AST\(assignVarName/),
|
44
|
+
'HasWhile' => use(/AST\(while/)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Mumukit::Inspection::TargetedInspection
|
50
|
+
include EvalExpectationsOnAST
|
51
|
+
|
52
|
+
def expectations
|
53
|
+
{
|
54
|
+
'HasArity' => lambda { |binding| /#{subject_for(binding).ast_regexp}\s*AST\((\s*\w+){#{target}}\)/ },
|
55
|
+
'HasRepeatOf' => check_repeat_of("AST\\(literal\\s*#{target}\\)"),
|
56
|
+
'HasUsage' => use(/AST\((proc|func)Call\s*#{target}$/)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Mumukit::Inspection::NegatedInspection
|
62
|
+
def eval_in_gobstones(binding, ast)
|
63
|
+
!@inspection.eval_in_gobstones(binding, ast)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class GobstonesExpectationsHook < Mumukit::Defaults::ExpectationsHook
|
68
|
+
include StonesSpec::WithTempfile
|
69
|
+
include WithTestParser
|
70
|
+
|
71
|
+
def run!(request)
|
72
|
+
content = request[:content]
|
73
|
+
expectations = request[:expectations]
|
74
|
+
|
75
|
+
if content.strip.empty?
|
76
|
+
return expectations.map { |exp| {'expectation' => exp, 'result' => false } }
|
77
|
+
end
|
78
|
+
|
79
|
+
ast = generate_ast! content
|
80
|
+
all_expectations = expectations + (default_expectations_for parse_test request)
|
81
|
+
|
82
|
+
all_expectations.map { |exp| {'expectation' => exp, 'result' => run_expectation!(exp, ast)} }
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def default_expectations_for(test)
|
88
|
+
StonesSpec::Subject.from(test[:subject]).default_expectations
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_expectation!(expectation, ast)
|
92
|
+
Mumukit::Inspection.parse(expectation['inspection']).eval_in_gobstones(expectation['binding'], ast)
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_ast!(source_code)
|
96
|
+
%x"#{gobstones_command} #{write_tempfile(source_code, 'gbs').path} --print-ast --target parse --no-print-retvals --silent"
|
97
|
+
end
|
98
|
+
|
99
|
+
def gobstones_command
|
100
|
+
@config['gobstones_command']
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mumukit'
|
2
|
+
|
3
|
+
Mumukit.runner_name = 'gobstones'
|
4
|
+
Mumukit.configure do |config|
|
5
|
+
config.structured = true
|
6
|
+
config.content_type = 'html'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'active_support/all'
|
10
|
+
require_relative './stones_spec'
|
11
|
+
require_relative './test_hook'
|
12
|
+
require_relative './metadata_hook'
|
13
|
+
require_relative './expectations_hook'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class GobstonesMetadataHook < Mumukit::Hook
|
2
|
+
def metadata
|
3
|
+
{language: {
|
4
|
+
name: 'gobstones',
|
5
|
+
icon: {type: 'devicon', name: 'gobstones'},
|
6
|
+
version: '1.4.1',
|
7
|
+
extension: 'gbs',
|
8
|
+
ace_mode: 'gobstones',
|
9
|
+
graphic: true
|
10
|
+
},
|
11
|
+
test_framework: {
|
12
|
+
name: 'stones-spec',
|
13
|
+
test_extension: 'yml'
|
14
|
+
}}
|
15
|
+
end
|
16
|
+
end
|
data/lib/stones_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'stones'
|
5
|
+
|
6
|
+
require_relative 'stones_spec/string'
|
7
|
+
require_relative 'stones_spec/hash'
|
8
|
+
|
9
|
+
require_relative 'stones_spec/with_tempfile'
|
10
|
+
require_relative 'stones_spec/with_command_line'
|
11
|
+
require_relative 'stones_spec/with_gbb_html_rendering'
|
12
|
+
require_relative 'stones_spec/version'
|
13
|
+
|
14
|
+
require_relative 'stones_spec/precondition'
|
15
|
+
require_relative 'stones_spec/postcondition/postcondition'
|
16
|
+
require_relative 'stones_spec/error_message_parser'
|
17
|
+
require_relative 'stones_spec/gobstones'
|
18
|
+
require_relative 'stones_spec/example'
|
19
|
+
require_relative 'stones_spec/subject'
|
20
|
+
require_relative 'stones_spec/runner'
|
21
|
+
require_relative 'stones_spec/html_board_renderer'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module ErrorMessageParser
|
3
|
+
def self.parse(result)
|
4
|
+
remove_boom_line_specification(remove_line_specification(result.lines)).join.strip
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.remove_line_specification(x)
|
10
|
+
x.drop_while { |str| !str.include_any? ['cerca de', 'Error de Gobstones'] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.remove_boom_line_specification(x)
|
14
|
+
x.take_while { |str| not str.strip.start_with? 'En:' }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module StonesSpec
|
4
|
+
class Example < OpenStruct
|
5
|
+
include Mumukit::WithCommandLine
|
6
|
+
|
7
|
+
def initialize(subject, attributes)
|
8
|
+
super attributes
|
9
|
+
@title = attributes[:title]
|
10
|
+
@subject = subject
|
11
|
+
@precondition = Precondition.from_example(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def execution_data(source)
|
15
|
+
{ source: @subject.test_program(source, @precondition.arguments),
|
16
|
+
initial_board: @precondition.initial_board_gbb }
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute!(files)
|
20
|
+
result, status = run_command "#{Gobstones.run(files[:source], files[:initial_board], files[:final_board])} 2>&1"
|
21
|
+
{ result: result, status: status }
|
22
|
+
end
|
23
|
+
|
24
|
+
def result(files, execution, postcondition)
|
25
|
+
if execution[:status] == :aborted
|
26
|
+
raise Gobstones::AbortedError, execution[:result]
|
27
|
+
end
|
28
|
+
|
29
|
+
if execution[:status] == :failed
|
30
|
+
error_message = Gobstones.parse_error_message execution[:result]
|
31
|
+
Gobstones.ensure_no_syntax_error! error_message
|
32
|
+
end
|
33
|
+
|
34
|
+
postcondition.validate(files[:initial_board].open.read, files[:final_board].open.read, execution[:result], execution[:status])
|
35
|
+
end
|
36
|
+
|
37
|
+
def title
|
38
|
+
@title || default_title
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def default_title
|
44
|
+
@subject.default_title @precondition.arguments
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Gobstones
|
3
|
+
def self.configure
|
4
|
+
@config ||= OpenStruct.new
|
5
|
+
yield @config
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.config
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.run(source_file, initial_board_file, final_board_file)
|
13
|
+
"#{config.gbs_command} #{source_file.path} --from #{initial_board_file.path} --to #{final_board_file.path} --no-print-board --silent"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_error_message(result)
|
17
|
+
"<pre>#{ErrorMessageParser.parse result}</pre>"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.runtime_error?(result)
|
21
|
+
result.include_any? ['Error en tiempo de ejecución', 'Error en el programa']
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.syntax_error?(result)
|
25
|
+
result.include? 'Error de sintaxis'
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.source_code_extension
|
29
|
+
'gbs'
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.board_extension
|
33
|
+
'gbb'
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.ensure_no_syntax_error!(error_message)
|
37
|
+
raise SyntaxError, error_message if syntax_error? error_message
|
38
|
+
end
|
39
|
+
|
40
|
+
class Error < Exception
|
41
|
+
end
|
42
|
+
|
43
|
+
class SyntaxError < Error
|
44
|
+
def status
|
45
|
+
:errored
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class AbortedError < Error
|
50
|
+
def status
|
51
|
+
:aborted
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative './with_gobstones_css'
|
2
|
+
|
3
|
+
module StonesSpec
|
4
|
+
class HtmlBoardRenderer
|
5
|
+
include StonesSpec::WithGobstonesCSS
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(board)
|
12
|
+
"<style type=\"text/css\">
|
13
|
+
#{render_css}</style>
|
14
|
+
|
15
|
+
#{render_html board}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name)
|
19
|
+
@options[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_html(board)
|
23
|
+
width, height = board.size
|
24
|
+
|
25
|
+
"#{table_title}
|
26
|
+
#{html_row_titles width, 'top'}
|
27
|
+
#{(0...height).to_a.reverse.map {|y| html_row(board, y)}.join}#{html_row_titles width, 'bottom'}
|
28
|
+
</table>
|
29
|
+
"
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_css
|
33
|
+
gobstones_css '9pt', 30
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def table_title
|
39
|
+
base = "<table class=\"gbs_board\">"
|
40
|
+
caption ? base + "\n<caption>#{caption}</caption>" : base
|
41
|
+
end
|
42
|
+
|
43
|
+
def html_row(board, y)
|
44
|
+
" <tr>
|
45
|
+
<td class=\"lv\">#{y}</td>
|
46
|
+
#{(0...board.size[0]).map {|x| html_cell board, [x, y] }.join "\n"}
|
47
|
+
<td class=\"lv\">#{y}</td>
|
48
|
+
</tr>
|
49
|
+
"
|
50
|
+
end
|
51
|
+
|
52
|
+
def html_cell(board, position)
|
53
|
+
cell = board.cell_at position
|
54
|
+
|
55
|
+
" <td class=\"gc#{board.head_position == position ? ' gh' : ''}\">
|
56
|
+
<table>
|
57
|
+
<tr>#{html_stone cell, :black}#{html_stone cell, :blue}</tr>
|
58
|
+
<tr>#{html_stone cell, :red}#{html_stone cell, :green}</tr>
|
59
|
+
</table>
|
60
|
+
</td>"
|
61
|
+
end
|
62
|
+
|
63
|
+
def html_stone(cell, color)
|
64
|
+
quantity = cell[color]
|
65
|
+
|
66
|
+
if cell[color] == 0
|
67
|
+
'<td><div class="O"></div></td>'
|
68
|
+
else
|
69
|
+
"<td><div class=\"gbs_stone #{Stones::Color.all_with_names.invert[color][0]}\"><span>#{quantity}</span></div></td>"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def html_row_titles(width, caption)
|
74
|
+
"<tr><td class=\"lx #{caption}_left\"></td>#{(0...width).map {|x| "<td class=\"lh\">#{x}</td>"}.join}<td class=\"lx #{caption}_right\"></td></tr>"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Postcondition
|
3
|
+
class ExpectedBoom
|
4
|
+
include StonesSpec::WithGbbHtmlRendering
|
5
|
+
|
6
|
+
attr_reader :example, :error_type
|
7
|
+
|
8
|
+
def initialize(example)
|
9
|
+
@example = example
|
10
|
+
@error_type = known_error_types[example.error.to_sym]
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate(initial_board_gbb, actual_final_board_gbb, result, status)
|
14
|
+
if status == :failed
|
15
|
+
check_right_error_type initial_board_gbb, result
|
16
|
+
else
|
17
|
+
boards = [['Tablero inicial', initial_board_gbb], ['Tablero final', actual_final_board_gbb]]
|
18
|
+
make_boards_output example.title, boards, :failed, failure_message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_right_error_type(initial_board_gbb, result)
|
25
|
+
if error_type_matches? result
|
26
|
+
[example.title, :passed, make_error_output(result, initial_board_gbb)]
|
27
|
+
else
|
28
|
+
[example.title, :failed, "#{invalid_boom_type_message}\n#{make_error_output(result, initial_board_gbb)}"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def error_type_matches?(result)
|
33
|
+
error_type[:matcher] =~ result
|
34
|
+
end
|
35
|
+
|
36
|
+
def known_error_types
|
37
|
+
{
|
38
|
+
out_of_board: { matcher: /La posición cae afuera del tablero/, friendly_message: 'caer fuera del tablero' },
|
39
|
+
no_stones: { matcher: /No hay bolitas de ese color/, friendly_message: 'no haber bolitas' },
|
40
|
+
unassigned_variable: { matcher: /podría no tener asignado ningún valor/, friendly_message: 'tener una variable sin asignar' },
|
41
|
+
wrong_argument_type: { matcher: /El argumento de .+ debería ser/, friendly_message: 'tipo erróneo de un argumento' },
|
42
|
+
wrong_arguments_quantity: { matcher: /Esperaba \d+ argumentos (.|\n)* Recibió \d+/, friendly_message: 'cantidad inválida de argumentos' }
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure_message
|
47
|
+
'Se esperaba que el programa hiciera BOOM pero se obtuvo un tablero final.'
|
48
|
+
end
|
49
|
+
|
50
|
+
def invalid_boom_type_message
|
51
|
+
"<p>Se esperaba que el programa hiciera BOOM por #{error_type[:friendly_message]}.</p>"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Postcondition
|
3
|
+
class ExpectedFinalBoard < ExpectedResult
|
4
|
+
attr_reader :check_head_position, :show_initial_board
|
5
|
+
|
6
|
+
def initialize(example, check_head_position, show_initial_board)
|
7
|
+
super example
|
8
|
+
@check_head_position = check_head_position
|
9
|
+
@show_initial_board = show_initial_board
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate_expected_result(initial_board_gbb, actual_final_board_gbb, _result)
|
13
|
+
if matches_with_expected_board? Stones::Gbb.read actual_final_board_gbb
|
14
|
+
passed_result initial_board_gbb, actual_final_board_gbb
|
15
|
+
else
|
16
|
+
failed_result initial_board_gbb, example.final_board, actual_final_board_gbb
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def failed_result(initial_board_gbb, expected_board_gbb, actual_board_gbb)
|
23
|
+
boards = [
|
24
|
+
['Tablero final esperado', expected_board_gbb],
|
25
|
+
['Tablero final obtenido', actual_board_gbb]
|
26
|
+
]
|
27
|
+
|
28
|
+
boards.unshift ['Tablero inicial', initial_board_gbb] if show_initial_board
|
29
|
+
|
30
|
+
make_boards_output example.title, boards, :failed
|
31
|
+
end
|
32
|
+
|
33
|
+
def passed_result(initial_board_gbb, actual_board_gbb)
|
34
|
+
boards = [
|
35
|
+
['Tablero final', actual_board_gbb]
|
36
|
+
]
|
37
|
+
|
38
|
+
boards.unshift ['Tablero inicial', initial_board_gbb] if show_initial_board
|
39
|
+
|
40
|
+
make_boards_output example.title, boards, :passed
|
41
|
+
end
|
42
|
+
|
43
|
+
def matches_with_expected_board?(actual_board)
|
44
|
+
if check_head_position
|
45
|
+
actual_board == final_board
|
46
|
+
else
|
47
|
+
actual_board.cells_equal? final_board
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def final_board
|
52
|
+
Stones::Gbb.read example.final_board
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Postcondition
|
3
|
+
class ExpectedReturnValue < ExpectedResult
|
4
|
+
def initialize(example, show_initial_board)
|
5
|
+
super example
|
6
|
+
@show_initial_board = show_initial_board
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate_expected_result(initial_board_gbb, _actual_final_board_gbb, result)
|
10
|
+
normalized_actual_return = parse_success_output(result).strip
|
11
|
+
|
12
|
+
if normalized_actual_return == return_value
|
13
|
+
make_result(:passed, initial_board_gbb)
|
14
|
+
else
|
15
|
+
make_result(:failed, initial_board_gbb, "Se esperaba <b>#{return_value}</b> pero se obtuvo <b>#{normalized_actual_return}</b>")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def parse_success_output(result)
|
22
|
+
first_return_value result || ''
|
23
|
+
end
|
24
|
+
|
25
|
+
def first_return_value(result)
|
26
|
+
result[/#1 -> (.+)/, 1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def make_result(status, initial_board_gbb, output='')
|
30
|
+
title = "#{example.title} -> #{return_value}"
|
31
|
+
|
32
|
+
if @show_initial_board
|
33
|
+
make_boards_output title, [['Tablero inicial', initial_board_gbb]], status, output
|
34
|
+
else
|
35
|
+
[title, status, output]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def return_value
|
40
|
+
example.return.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Postcondition
|
3
|
+
def self.from(example, check_head_position, show_initial_board)
|
4
|
+
if example.final_board
|
5
|
+
ExpectedFinalBoard.new(example, check_head_position, show_initial_board)
|
6
|
+
elsif example.return
|
7
|
+
ExpectedReturnValue.new(example, show_initial_board)
|
8
|
+
else
|
9
|
+
ExpectedBoom.new(example)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ExpectedResult
|
14
|
+
include StonesSpec::WithGbbHtmlRendering
|
15
|
+
|
16
|
+
attr_reader :example
|
17
|
+
|
18
|
+
def initialize(example)
|
19
|
+
@example = example
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(initial_board_gbb, actual_final_board_gbb, result, status)
|
23
|
+
if status == :failed
|
24
|
+
[example.title, :failed, make_error_output(result, initial_board_gbb)]
|
25
|
+
else
|
26
|
+
validate_expected_result(initial_board_gbb, actual_final_board_gbb, result)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require_relative './expected_boom'
|
34
|
+
require_relative './expected_final_board'
|
35
|
+
require_relative './expected_return_value'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
class Precondition
|
3
|
+
attr_reader :initial_board_gbb
|
4
|
+
|
5
|
+
def self.from_example(example)
|
6
|
+
self.new example.initial_board, example.arguments
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(initial_board, arguments)
|
10
|
+
@initial_board_gbb = initial_board || default_initial_board
|
11
|
+
@arguments = arguments
|
12
|
+
end
|
13
|
+
|
14
|
+
def arguments
|
15
|
+
@arguments || []
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def default_initial_board
|
21
|
+
'GBB/1.0
|
22
|
+
size 4 4
|
23
|
+
head 0 0'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
class Runner
|
3
|
+
include StonesSpec::WithTempfile
|
4
|
+
|
5
|
+
def run!(test_definition)
|
6
|
+
subject = Subject.from(test_definition[:subject])
|
7
|
+
source = test_definition[:source]
|
8
|
+
check_head_position = test_definition[:check_head_position]
|
9
|
+
show_initial_board = test_definition.fetch(:show_initial_board, true)
|
10
|
+
|
11
|
+
begin
|
12
|
+
[test_definition[:examples].map do |example_definition|
|
13
|
+
run_example!(example_definition, check_head_position, show_initial_board, source, subject)
|
14
|
+
end]
|
15
|
+
rescue Gobstones::AbortedError => e
|
16
|
+
test_definition[:expect_endless_while] ? [e.message, :passed] : [e.message, e.status]
|
17
|
+
rescue Gobstones::Error => e
|
18
|
+
[e.message, e.status]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def run_example!(example_definition, check_head_position, show_initial_board, source, subject)
|
25
|
+
example = Example.new(subject, example_definition)
|
26
|
+
|
27
|
+
data = example.execution_data source
|
28
|
+
files = {
|
29
|
+
source: Gobstones.source_code_extension,
|
30
|
+
initial_board: Gobstones.board_extension,
|
31
|
+
final_board: Gobstones.board_extension
|
32
|
+
}.map_values { |name, extension| write_tempfile(data[name], extension) }
|
33
|
+
|
34
|
+
execution = example.execute! files
|
35
|
+
|
36
|
+
example.result files, execution, Postcondition.from(example, check_head_position, show_initial_board)
|
37
|
+
ensure
|
38
|
+
files.each_value(&:unlink)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Subject
|
3
|
+
def self.from(name)
|
4
|
+
if name
|
5
|
+
infer_subject_type_for(name).new(name)
|
6
|
+
else
|
7
|
+
Program
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.infer_subject_type_for(string)
|
12
|
+
string.start_with_lowercase? ? StonesSpec::Subject::Function : StonesSpec::Subject::Procedure
|
13
|
+
end
|
14
|
+
|
15
|
+
module Program
|
16
|
+
def self.test_program(source, _arguments)
|
17
|
+
source
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default_title(_arguments)
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.ast_regexp
|
25
|
+
/AST\(entrypoint\s*program/
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.default_expectations
|
29
|
+
[{ 'binding' => 'program', 'inspection' => 'HasBinding' }]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Callable
|
34
|
+
def initialize(name)
|
35
|
+
@name = name
|
36
|
+
end
|
37
|
+
|
38
|
+
def call_string(arguments)
|
39
|
+
"#{@name}(#{arguments.join(', ')})"
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_title(arguments)
|
43
|
+
call_string arguments
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_expectations
|
47
|
+
[ { 'binding' => 'program', 'inspection' => 'Not:HasBinding' },
|
48
|
+
{ 'binding' => "#{@name}", 'inspection' => 'HasBinding' } ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Procedure < Callable
|
53
|
+
def test_program(source, arguments)
|
54
|
+
"program {
|
55
|
+
#{call_string arguments}
|
56
|
+
}
|
57
|
+
|
58
|
+
#{source}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def ast_regexp
|
62
|
+
/AST\(procedure\s*#{@name}$/
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Function < Callable
|
67
|
+
def test_program(source, arguments)
|
68
|
+
"program {
|
69
|
+
return (#{call_string arguments})
|
70
|
+
}
|
71
|
+
|
72
|
+
#{source}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def ast_regexp
|
76
|
+
/AST\(function\s*#{@name}/
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module WithGbbHtmlRendering
|
3
|
+
def get_html_board(caption, gbb_representation)
|
4
|
+
HtmlBoardRenderer.new(caption: caption).render(Stones::GbbReader.new.from_string gbb_representation)
|
5
|
+
end
|
6
|
+
|
7
|
+
def make_error_output(result, initial_board_gbb)
|
8
|
+
error_message = Gobstones.parse_error_message result
|
9
|
+
if Gobstones.runtime_error? error_message
|
10
|
+
"#{get_html_board 'Tablero inicial', initial_board_gbb}\n#{get_boom_board initial_board_gbb}\n#{error_message}"
|
11
|
+
else
|
12
|
+
error_message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_boards_output(title, gbb_boards, status, extra = nil)
|
17
|
+
boards = gbb_boards.map { |gbb_with_caption| get_html_board *gbb_with_caption }.join("\n")
|
18
|
+
output = "<div>#{boards}</div>"
|
19
|
+
|
20
|
+
output = "<p>#{extra}</p>\n#{output}" if extra
|
21
|
+
|
22
|
+
[title, status, output]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def get_boom_board(initial_board_gbb)
|
28
|
+
gbb = empty_board_gbb_like initial_board_gbb
|
29
|
+
|
30
|
+
boom_css =
|
31
|
+
"<style type=\"text/css\">
|
32
|
+
table.boom {
|
33
|
+
background-image: url('#{boom_image_url}');
|
34
|
+
background-size: contain;
|
35
|
+
background-repeat: no-repeat;
|
36
|
+
background-position: center;
|
37
|
+
}
|
38
|
+
</style>"
|
39
|
+
|
40
|
+
without_header with_boom_css_class "#{boom_css}\n#{get_html_board '¡Se produjo BOOM!', gbb}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def boom_image_url
|
44
|
+
'https://raw.githubusercontent.com/mumuki/mumuki-gobstones-server/master/lib/assets/boom.png'
|
45
|
+
end
|
46
|
+
|
47
|
+
def empty_board_gbb_like(initial_board_gbb)
|
48
|
+
x, y = Stones::Gbb.read(initial_board_gbb).size
|
49
|
+
Stones::Gbb.write Stones::Board.empty(x, y)
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_boom_css_class(html)
|
53
|
+
html.sub('class="gbs_board"', 'class="gbs_board boom"')
|
54
|
+
end
|
55
|
+
|
56
|
+
def without_header(html)
|
57
|
+
html.sub('class="gc gh"', 'class="gc"')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module WithGobstonesCSS
|
3
|
+
def gobstones_css(font_size, size)
|
4
|
+
unit = 'px'
|
5
|
+
|
6
|
+
full_size = "#{size}#{unit}"
|
7
|
+
half_size = "#{size / 2}#{unit}"
|
8
|
+
|
9
|
+
"table.gbs_board {
|
10
|
+
border-style: none;
|
11
|
+
border: solid black 0px;
|
12
|
+
border-spacing: 0;
|
13
|
+
border-collapse: collapse;
|
14
|
+
font-family: Arial, Helvetica, sans-serif;
|
15
|
+
font-size: #{font_size};
|
16
|
+
display: inline-block;
|
17
|
+
vertical-align: top;
|
18
|
+
}
|
19
|
+
.gbs_board td {
|
20
|
+
margin: 0;
|
21
|
+
padding: 2px;
|
22
|
+
border: solid #888 1px;
|
23
|
+
width: #{full_size};
|
24
|
+
height: #{full_size};
|
25
|
+
}
|
26
|
+
.gbs_board td.gh { /* position of the header in the board */
|
27
|
+
margin: 0;
|
28
|
+
padding: 2px;
|
29
|
+
border: dotted #440 3px;
|
30
|
+
background: #dd8;
|
31
|
+
width: #{full_size};
|
32
|
+
height: #{full_size};
|
33
|
+
}
|
34
|
+
.gbs_board td.lv { /* labels at the side */
|
35
|
+
text-align: center;
|
36
|
+
vertical-align: middle;
|
37
|
+
border-style: none;
|
38
|
+
border: solid black 0px;
|
39
|
+
background: #ddd;
|
40
|
+
width: #{half_size};
|
41
|
+
}
|
42
|
+
.gbs_board td.lh { /* labels at the top / bottom */
|
43
|
+
text-align: center;
|
44
|
+
vertical-align: middle;
|
45
|
+
border-style: none;
|
46
|
+
border: solid black 0px;
|
47
|
+
background: #ddd;
|
48
|
+
height: #{half_size};
|
49
|
+
}
|
50
|
+
.gbs_board td.lx { /* corner */
|
51
|
+
border-style: none;
|
52
|
+
border: solid black 0px;
|
53
|
+
background: #ddd;
|
54
|
+
width: #{half_size};
|
55
|
+
height: #{half_size};
|
56
|
+
}
|
57
|
+
.gbs_board td.top_left {
|
58
|
+
-webkit-border-top-left-radius: 10px;
|
59
|
+
-moz-border-top-left-radius: 10px;
|
60
|
+
border-top-left-radius: 10px;
|
61
|
+
}
|
62
|
+
.gbs_board td.top_right {
|
63
|
+
-webkit-border-top-right-radius: 10px;
|
64
|
+
-moz-border-top-right-radius: 10px;
|
65
|
+
border-top-right-radius: 10px;
|
66
|
+
}
|
67
|
+
.gbs_board td.bottom_left {
|
68
|
+
-webkit-border-bottom-left-radius: 10px;
|
69
|
+
-moz-border-bottom-left-radius: 10px;
|
70
|
+
border-bottom-left-radius: 10px;
|
71
|
+
}
|
72
|
+
.gbs_board td.bottom_right {
|
73
|
+
-webkit-border-bottom-right-radius: 10px;
|
74
|
+
-moz-border-bottom-right-radius: 10px;
|
75
|
+
border-bottom-right-radius: 10px;
|
76
|
+
}
|
77
|
+
.gbs_board table.gc { /* cell table */
|
78
|
+
border-style: none;
|
79
|
+
border: solid black 0px;
|
80
|
+
}
|
81
|
+
.gbs_board .gc tr {
|
82
|
+
border-style: none;
|
83
|
+
border: 0px;
|
84
|
+
}
|
85
|
+
.gbs_board .gc td {
|
86
|
+
border-style: none;
|
87
|
+
border: solid black 0px;
|
88
|
+
width: #{half_size};
|
89
|
+
height: #{half_size};
|
90
|
+
text-align: center;
|
91
|
+
color: black;
|
92
|
+
}
|
93
|
+
.gbs_board .gc td div {
|
94
|
+
line-height: 2;
|
95
|
+
}
|
96
|
+
.gbs_board div.A { background: #88f; border: solid 1px #008; }
|
97
|
+
.gbs_board div.N { background: #aaa; border: solid 1px #222; }
|
98
|
+
.gbs_board div.R { background: #f88; border: solid 1px #800; }
|
99
|
+
.gbs_board div.V { background: #8f8; border: solid 1px #080; }
|
100
|
+
.gbs_board div.O { width: 20px; height: 20px; background: none; } /* empty */
|
101
|
+
.gbs_stone {
|
102
|
+
font-weight: bold;
|
103
|
+
font-size: 8pt;
|
104
|
+
width: 20px;
|
105
|
+
height: 20px;
|
106
|
+
-webkit-border-radius: 10px;
|
107
|
+
-moz-border-radius: 10px;
|
108
|
+
border-radius: 10px;
|
109
|
+
}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/test_hook.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'mumukit'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require_relative 'stones_spec'
|
5
|
+
require_relative 'with_test_parser'
|
6
|
+
|
7
|
+
class GobstonesTestHook < Mumukit::Hook
|
8
|
+
include WithTestParser
|
9
|
+
|
10
|
+
def compile(request)
|
11
|
+
test = parse_test request
|
12
|
+
test[:source] = "#{request[:content]}\n#{request[:extra]}".strip
|
13
|
+
test[:check_head_position] = !!test[:check_head_position]
|
14
|
+
test
|
15
|
+
end
|
16
|
+
|
17
|
+
def run!(test_definition)
|
18
|
+
StonesSpec::Gobstones.configure do |config|
|
19
|
+
config.gbs_command = gobstones_command
|
20
|
+
end
|
21
|
+
|
22
|
+
StonesSpec::Runner.new.run!(test_definition)
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mumuki-gobstones-runner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Franco Leonardo Bulgarelli
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-27 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.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: codeclimate-test-reporter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mumukit-bridge
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- franco@mumuki.org
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- lib/assets/boom.png
|
105
|
+
- lib/expectations_hook.rb
|
106
|
+
- lib/gobstones_runner.rb
|
107
|
+
- lib/metadata_hook.rb
|
108
|
+
- lib/stones_spec.rb
|
109
|
+
- lib/stones_spec/error_message_parser.rb
|
110
|
+
- lib/stones_spec/example.rb
|
111
|
+
- lib/stones_spec/gobstones.rb
|
112
|
+
- lib/stones_spec/hash.rb
|
113
|
+
- lib/stones_spec/html_board_renderer.rb
|
114
|
+
- lib/stones_spec/postcondition/expected_boom.rb
|
115
|
+
- lib/stones_spec/postcondition/expected_final_board.rb
|
116
|
+
- lib/stones_spec/postcondition/expected_return_value.rb
|
117
|
+
- lib/stones_spec/postcondition/postcondition.rb
|
118
|
+
- lib/stones_spec/precondition.rb
|
119
|
+
- lib/stones_spec/runner.rb
|
120
|
+
- lib/stones_spec/string.rb
|
121
|
+
- lib/stones_spec/subject.rb
|
122
|
+
- lib/stones_spec/version.rb
|
123
|
+
- lib/stones_spec/with_command_line.rb
|
124
|
+
- lib/stones_spec/with_gbb_html_rendering.rb
|
125
|
+
- lib/stones_spec/with_gobstones_css.rb
|
126
|
+
- lib/stones_spec/with_tempfile.rb
|
127
|
+
- lib/test_hook.rb
|
128
|
+
- lib/with_test_parser.rb
|
129
|
+
homepage: http://github.com/mumuki/mumuki-gobstones-server
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ! '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.4.5
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Gobstones Runner for Mumuki
|
153
|
+
test_files: []
|