stones-spec 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|