stones-spec 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/example.rb +43 -0
- data/lib/gobstones.rb +26 -0
- data/lib/helpers/hash.rb +5 -0
- data/lib/helpers/string.rb +10 -0
- data/lib/helpers/with_tempfile.rb +13 -0
- data/lib/postcondition/expected_boom.rb +55 -0
- data/lib/postcondition/expected_final_board.rb +56 -0
- data/lib/postcondition/expected_return_value.rb +44 -0
- data/lib/postcondition/postcondition.rb +35 -0
- data/lib/precondition.rb +26 -0
- data/lib/renderers/html_board_renderer.rb +77 -0
- data/lib/renderers/with_gbb_html_rendering.rb +55 -0
- data/lib/renderers/with_gobstones_css.rb +112 -0
- data/lib/runner.rb +45 -0
- data/lib/stones-spec.rb +21 -0
- data/lib/subject.rb +80 -0
- data/lib/version.rb +3 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61404af586ca76a2bbe93f918c43e9e5f96d6e27
|
4
|
+
data.tar.gz: 6a5c5604f68519886817fd96e6c8d1ebe811d2ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 30bfcb15abdf1f02ae85ba42def8c1d74bf91d0c5924fbc3be60a383a458e1cae152743a7c7ed0d560115746eb52f5f3160bf58a441371169f06aefd1c064ee2
|
7
|
+
data.tar.gz: c2c67ed0a00ff8c5ebe54f227448efc184f3e2fb40d55c19d0eed7f3bb2ec7f449519a1d9e1a8a8bfe975631abb7dbc526beec8b228fe3827670bc79dfa83435
|
data/lib/example.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module StonesSpec
|
4
|
+
class Example < OpenStruct
|
5
|
+
def initialize(subject, attributes)
|
6
|
+
super attributes
|
7
|
+
@title = attributes[:title]
|
8
|
+
@subject = subject
|
9
|
+
@precondition = Precondition.from_example(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def execution_data(source)
|
13
|
+
{ source: @subject.test_program(source, @precondition.arguments),
|
14
|
+
initial_board: @precondition.initial_board_gbb }
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute!(files, parser)
|
18
|
+
parser.run(files[:source], files[:initial_board], files[:final_board])
|
19
|
+
end
|
20
|
+
|
21
|
+
def result(files, execution, postcondition)
|
22
|
+
if execution[:status] == :syntax_error
|
23
|
+
raise Gobstones::SyntaxError, execution[:result]
|
24
|
+
end
|
25
|
+
|
26
|
+
if execution[:status] == :runtime_error
|
27
|
+
raise Gobstones::AbortedError, execution[:result]
|
28
|
+
end
|
29
|
+
|
30
|
+
postcondition.validate(files[:initial_board].open.read, files[:final_board].open.read, execution[:result], execution[:status])
|
31
|
+
end
|
32
|
+
|
33
|
+
def title
|
34
|
+
@title || default_title
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def default_title
|
40
|
+
@subject.default_title @precondition.arguments
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/gobstones.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
module Gobstones
|
3
|
+
def self.source_code_extension
|
4
|
+
'gbs'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.board_extension
|
8
|
+
'gbb'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Error < Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
class SyntaxError < Error
|
15
|
+
def status
|
16
|
+
:errored
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class AbortedError < Error
|
21
|
+
def status
|
22
|
+
:aborted
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/helpers/hash.rb
ADDED
@@ -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'
|
data/lib/precondition.rb
ADDED
@@ -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,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 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
|
+
"#{get_html_board 'Tablero inicial', initial_board_gbb}\n#{get_boom_board initial_board_gbb}\n#{result}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_boards_output(title, gbb_boards, status, extra = nil)
|
12
|
+
boards = gbb_boards.map { |gbb_with_caption| get_html_board *gbb_with_caption }.join("\n")
|
13
|
+
output = "<div>#{boards}</div>"
|
14
|
+
|
15
|
+
output = "<p>#{extra}</p>\n#{output}" if extra
|
16
|
+
|
17
|
+
[title, status, output]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def get_boom_board(initial_board_gbb)
|
23
|
+
gbb = empty_board_gbb_like initial_board_gbb
|
24
|
+
|
25
|
+
boom_css =
|
26
|
+
"<style type=\"text/css\">
|
27
|
+
table.boom {
|
28
|
+
background-image: url('#{boom_image_url}');
|
29
|
+
background-size: contain;
|
30
|
+
background-repeat: no-repeat;
|
31
|
+
background-position: center;
|
32
|
+
}
|
33
|
+
</style>"
|
34
|
+
|
35
|
+
without_header with_boom_css_class "#{boom_css}\n#{get_html_board '¡Se produjo BOOM!', gbb}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def boom_image_url
|
39
|
+
'https://raw.githubusercontent.com/mumuki/mumuki-gobstones-server/master/lib/assets/boom.png'
|
40
|
+
end
|
41
|
+
|
42
|
+
def empty_board_gbb_like(initial_board_gbb)
|
43
|
+
x, y = Stones::Gbb.read(initial_board_gbb).size
|
44
|
+
Stones::Gbb.write Stones::Board.empty(x, y)
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_boom_css_class(html)
|
48
|
+
html.sub('class="gbs_board"', 'class="gbs_board boom"')
|
49
|
+
end
|
50
|
+
|
51
|
+
def without_header(html)
|
52
|
+
html.sub('class="gc gh"', 'class="gc"')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
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/runner.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
class Runner
|
3
|
+
include StonesSpec::WithTempfile
|
4
|
+
|
5
|
+
def initialize(parser)
|
6
|
+
@parser = parser
|
7
|
+
end
|
8
|
+
|
9
|
+
def run!(test_definition)
|
10
|
+
subject = Subject.from(test_definition[:subject])
|
11
|
+
source = test_definition[:source]
|
12
|
+
check_head_position = test_definition[:check_head_position]
|
13
|
+
show_initial_board = test_definition.fetch(:show_initial_board, true)
|
14
|
+
|
15
|
+
begin
|
16
|
+
[test_definition[:examples].map do |example_definition|
|
17
|
+
run_example!(example_definition, check_head_position, show_initial_board, source, subject)
|
18
|
+
end]
|
19
|
+
rescue Gobstones::AbortedError => e
|
20
|
+
test_definition[:expect_endless_while] ? [e.message, :passed] : [e.message, e.status]
|
21
|
+
rescue Gobstones::Error => e
|
22
|
+
[e.message, e.status]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def run_example!(example_definition, check_head_position, show_initial_board, source, subject)
|
29
|
+
example = Example.new(subject, example_definition)
|
30
|
+
|
31
|
+
data = example.execution_data source
|
32
|
+
files = {
|
33
|
+
source: Gobstones.source_code_extension,
|
34
|
+
initial_board: Gobstones.board_extension,
|
35
|
+
final_board: Gobstones.board_extension
|
36
|
+
}.map_values { |name, extension| write_tempfile(data[name], extension) }
|
37
|
+
|
38
|
+
execution = example.execute! files, @parser
|
39
|
+
|
40
|
+
example.result files, execution, Postcondition.from(example, check_head_position, show_initial_board)
|
41
|
+
ensure
|
42
|
+
files.each_value(&:unlink)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/stones-spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module StonesSpec
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'stones'
|
5
|
+
|
6
|
+
require_relative './helpers/string'
|
7
|
+
require_relative './helpers/hash'
|
8
|
+
require_relative './helpers/with_tempfile'
|
9
|
+
|
10
|
+
require_relative './version'
|
11
|
+
require_relative './gobstones'
|
12
|
+
|
13
|
+
require_relative './renderers/html_board_renderer'
|
14
|
+
require_relative './renderers/with_gbb_html_rendering'
|
15
|
+
|
16
|
+
require_relative './precondition'
|
17
|
+
require_relative './postcondition/postcondition'
|
18
|
+
|
19
|
+
require_relative './example'
|
20
|
+
require_relative './subject'
|
21
|
+
require_relative './runner'
|
data/lib/subject.rb
ADDED
@@ -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
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stones-spec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mumuki
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-stones
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- mumuki@mumuki.org
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- lib/example.rb
|
63
|
+
- lib/gobstones.rb
|
64
|
+
- lib/helpers/hash.rb
|
65
|
+
- lib/helpers/string.rb
|
66
|
+
- lib/helpers/with_tempfile.rb
|
67
|
+
- lib/postcondition/expected_boom.rb
|
68
|
+
- lib/postcondition/expected_final_board.rb
|
69
|
+
- lib/postcondition/expected_return_value.rb
|
70
|
+
- lib/postcondition/postcondition.rb
|
71
|
+
- lib/precondition.rb
|
72
|
+
- lib/renderers/html_board_renderer.rb
|
73
|
+
- lib/renderers/with_gbb_html_rendering.rb
|
74
|
+
- lib/renderers/with_gobstones_css.rb
|
75
|
+
- lib/runner.rb
|
76
|
+
- lib/stones-spec.rb
|
77
|
+
- lib/subject.rb
|
78
|
+
- lib/version.rb
|
79
|
+
homepage: http://github.com/mumuki/stones-spec
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.5.1
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Minimal test framework for Gobstones
|
103
|
+
test_files: []
|