mumuki-gobstones-runner 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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=
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
@@ -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,5 @@
1
+ class Hash
2
+ def map_values(&block)
3
+ Hash[self.map {|k, v| [k, block.call(k,v)] }]
4
+ end
5
+ 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,10 @@
1
+ class String
2
+ def start_with_lowercase?
3
+ first_letter = self[0]
4
+ first_letter.downcase == first_letter
5
+ end
6
+
7
+ def include_any?(other_strs)
8
+ other_strs.any? { |other| include? other }
9
+ end
10
+ 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,3 @@
1
+ module StonesSpec
2
+ VERSION = '0.3.0'
3
+ end
@@ -0,0 +1,7 @@
1
+ module StonesSpec
2
+ module WithCommandLine
3
+ def run_command(command)
4
+ [%x{#{command}}, $?.success? ? :passed : :failed]
5
+ end
6
+ end
7
+ 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
@@ -0,0 +1,13 @@
1
+ require 'tempfile'
2
+
3
+ module StonesSpec
4
+ module WithTempfile
5
+
6
+ def write_tempfile(content, extension)
7
+ file = Tempfile.new %W(gobstones. .#{extension})
8
+ file.write content
9
+ file.close
10
+ file
11
+ end
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'yaml'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ module WithTestParser
5
+ def parse_test(request)
6
+ (YAML.load request[:test]).deep_symbolize_keys
7
+ end
8
+ 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: []