cukable 0.1.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.
- data/.yardopts +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +47 -0
- data/README.md +368 -0
- data/Rakefile +41 -0
- data/bin/cuke2fit +25 -0
- data/cukable.gemspec +26 -0
- data/features/conversion.feature +54 -0
- data/features/cuke_fixture.feature +147 -0
- data/features/slim_json_formatter.feature +153 -0
- data/features/step_definitions/cukable_steps.rb +115 -0
- data/features/support/env.rb +183 -0
- data/lib/cukable.rb +2 -0
- data/lib/cukable/conversion.rb +314 -0
- data/lib/cukable/cuke.rb +305 -0
- data/lib/cukable/helper.rb +221 -0
- data/lib/cukable/slim_json_formatter.rb +366 -0
- data/spec/conversion_spec.rb +275 -0
- data/spec/cuke_spec.rb +134 -0
- data/spec/helper_spec.rb +203 -0
- data/spec/spec_helper.rb +10 -0
- data/vendor/cache/rubyslim-0.1.1.gem +0 -0
- metadata +188 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '../../lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
class CukableHelper
|
8
|
+
def initialize
|
9
|
+
remove_test_dir
|
10
|
+
create_test_dir
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def test_dir
|
15
|
+
@test_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../self_test'))
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# Execute `block` within `test_dir`
|
20
|
+
def in_test_dir(&block)
|
21
|
+
Dir.chdir(test_dir, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Create a standard cucumber features/ directory in `test_dir`
|
26
|
+
def create_standard_cucumber_dir
|
27
|
+
in_test_dir do
|
28
|
+
FileUtils.mkdir_p 'features/support'
|
29
|
+
FileUtils.mkdir 'features/step_definitions'
|
30
|
+
create_env_rb
|
31
|
+
create_stepdefs
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def create_standard_fitnesse_dir
|
37
|
+
in_test_dir do
|
38
|
+
FileUtils.mkdir_p 'FitNesseRoot'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def create_test_dir
|
44
|
+
FileUtils.mkdir_p test_dir
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def remove_test_dir
|
49
|
+
FileUtils.rm_rf test_dir
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def create_fitnesse_page(page_name, content)
|
54
|
+
in_test_dir do
|
55
|
+
page_dir = File.join('FitNesseRoot', page_name)
|
56
|
+
page_file = File.join(page_dir, 'content.txt')
|
57
|
+
FileUtils.mkdir_p page_dir
|
58
|
+
File.open(page_file, 'w') do |file|
|
59
|
+
file.puts(content)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Create features/support/env.rb with necessary configuration for running
|
66
|
+
# cucumber there
|
67
|
+
def create_env_rb
|
68
|
+
in_test_dir do
|
69
|
+
File.open('features/support/env.rb', 'w') do |file|
|
70
|
+
file.puts "$:.unshift File.join(File.dirname(__FILE__), '../../../lib')"
|
71
|
+
file.puts "require 'cukable/slim_json_formatter'"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def create_stepdefs
|
78
|
+
in_test_dir do
|
79
|
+
File.open('features/step_definitions/simple_steps.rb', 'w') do |file|
|
80
|
+
file.puts <<-EOF
|
81
|
+
Given /^a step passes$/ do
|
82
|
+
true.should == true
|
83
|
+
end
|
84
|
+
Given /^a step fails$/ do
|
85
|
+
true.should == false
|
86
|
+
end
|
87
|
+
Given /^a step is skipped$/ do
|
88
|
+
true.should == true
|
89
|
+
end
|
90
|
+
Given /^I have a table:$/ do |table|
|
91
|
+
table.raw.each do |row|
|
92
|
+
row.each do |cell|
|
93
|
+
cell.should == 'OK'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
EOF
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# Create a file relative to `test_dir`
|
104
|
+
def create_file(filename, content)
|
105
|
+
in_test_dir do
|
106
|
+
FileUtils.mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
|
107
|
+
File.open(filename, 'w') { |f| f << content }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# Run cucumber with the given command-line argument string
|
113
|
+
def run_cucumber(args)
|
114
|
+
stderr_file = Tempfile.new('cucumber')
|
115
|
+
stderr_file.close
|
116
|
+
in_test_dir do
|
117
|
+
mode = Cucumber::RUBY_1_9 ? {:external_encoding=>"UTF-8"} : 'r'
|
118
|
+
IO.popen("cucumber #{args} 2> #{stderr_file.path}", mode) do |io|
|
119
|
+
@last_stdout = io.read
|
120
|
+
puts @last_stdout
|
121
|
+
end
|
122
|
+
#@last_exit_status = $?.exitstatus
|
123
|
+
end
|
124
|
+
@last_stderr = IO.read(stderr_file.path)
|
125
|
+
puts @last_stderr
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Ensure that the given file contains exactly the given text
|
130
|
+
# (extra newlines/whitespace at beginning or end don't count)
|
131
|
+
def file_should_contain(filename, text)
|
132
|
+
in_test_dir do
|
133
|
+
IO.read(filename).strip.should == text.strip
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# Ensure that the given filename contains JSON text.
|
139
|
+
#
|
140
|
+
# JSON does not need to match exactly; the output of each line
|
141
|
+
# should *start* with the expected JSON text, but could contain
|
142
|
+
# additional stuff afterwards.
|
143
|
+
def file_should_contain_json(filename, json_text)
|
144
|
+
in_test_dir do
|
145
|
+
got_json = JSON.load(File.open(filename))
|
146
|
+
want_json = JSON.parse(json_text)
|
147
|
+
|
148
|
+
got_json.zip(want_json).each do |got_row, want_row|
|
149
|
+
got_row.zip(want_row).each do |got, want|
|
150
|
+
got.should =~ /^#{want}/
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def xml_content(type)
|
158
|
+
return [
|
159
|
+
'<?xml version="1.0"?>',
|
160
|
+
'<properties>',
|
161
|
+
' <Edit>true</Edit>',
|
162
|
+
' <Files>true</Files>',
|
163
|
+
' <Properties>true</Properties>',
|
164
|
+
' <RecentChanges>true</RecentChanges>',
|
165
|
+
' <Refactor>true</Refactor>',
|
166
|
+
' <Search>true</Search>',
|
167
|
+
" <#{type}/>",
|
168
|
+
' <Versions>true</Versions>',
|
169
|
+
' <WhereUsed>true</WhereUsed>',
|
170
|
+
'</properties>',
|
171
|
+
].join("\n")
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
World do
|
177
|
+
CukableHelper.new
|
178
|
+
end
|
179
|
+
|
180
|
+
After do
|
181
|
+
#remove_test_dir
|
182
|
+
end
|
183
|
+
|
data/lib/cukable.rb
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
# conversion.rb
|
2
|
+
|
3
|
+
# FIXME: This is a hack to support running cucumber features.
|
4
|
+
# May have unwanted side-effects.
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__), '..')
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
require 'cukable/helper'
|
9
|
+
|
10
|
+
module Cukable
|
11
|
+
# Supporting functions for converting Cucumber features to
|
12
|
+
# FitNesse wiki pages and vice-versa.
|
13
|
+
module Conversion
|
14
|
+
|
15
|
+
# Wikify the given feature, and return lines of FitNesse wikitext.
|
16
|
+
#
|
17
|
+
# @param [Array, File] feature
|
18
|
+
# An iterable that yields each line of a Cucumber feature. May be
|
19
|
+
# an open File object, or an Array of strings.
|
20
|
+
#
|
21
|
+
# @return [Array]
|
22
|
+
# FitNesse wikitext as a list of strings
|
23
|
+
#
|
24
|
+
def feature_to_fitnesse(feature)
|
25
|
+
|
26
|
+
# Unparsed text (between 'Feature:' line and the first Background/Scenario)
|
27
|
+
unparsed = []
|
28
|
+
# Table (all Background, Scenario, and Scenario Outlines with steps
|
29
|
+
table = ["!| Table: Cuke |"]
|
30
|
+
|
31
|
+
# Are we in the unparsed-text section of the .feature file?
|
32
|
+
in_unparsed = false
|
33
|
+
|
34
|
+
feature.each do |line|
|
35
|
+
line.strip!
|
36
|
+
|
37
|
+
# The Feature: line starts the table, and also starts the unparsed
|
38
|
+
# section of the feature file
|
39
|
+
if line =~ /^Feature:.*$/
|
40
|
+
table << "| #{line} |"
|
41
|
+
in_unparsed = true
|
42
|
+
|
43
|
+
# When the first Background/Scenario block is reached, end the unparsed
|
44
|
+
# text section
|
45
|
+
elsif line =~ /^(Background:|Scenario:|Scenario Outline:)/
|
46
|
+
in_unparsed = false
|
47
|
+
table << "| #{line} |"
|
48
|
+
|
49
|
+
# If line contains one or more tags, output them on separate lines
|
50
|
+
elsif line =~ /^\s*@\w+/
|
51
|
+
for tag in line.split
|
52
|
+
table << "| #{tag} |"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Between 'Feature:...' and the first Background/Scenario/Scenario Outline
|
56
|
+
# block, we're in the unparsed text section
|
57
|
+
elsif in_unparsed and !line.empty?
|
58
|
+
unparsed << line
|
59
|
+
|
60
|
+
# If line contains a table row, insert a '|' at the beginning
|
61
|
+
elsif line =~ /^\|.+\|$/
|
62
|
+
table << "| #{line}"
|
63
|
+
|
64
|
+
# If line is commented out, skip it
|
65
|
+
elsif line =~ /^#.*$/
|
66
|
+
nil
|
67
|
+
|
68
|
+
# Otherwise, if line is non-empty, insert a '|' at beginning and end
|
69
|
+
elsif !line.empty?
|
70
|
+
table << "| #{line} |"
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# If there was unparsed text, include an empty line after it
|
75
|
+
if !unparsed.empty?
|
76
|
+
unparsed << ''
|
77
|
+
end
|
78
|
+
return unparsed + table
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Return an array of Cucumber tables found in the given FitNesse content
|
83
|
+
# file, or an empty array if no tables are found. Each table in the array
|
84
|
+
# is in the same format as a table passed to the `do_table` method; that is,
|
85
|
+
# a table is an array of rows, where each row is an array of strings found
|
86
|
+
# in each cell of the table.
|
87
|
+
#
|
88
|
+
# @param [Array, File] wiki_page
|
89
|
+
# An iterable that yields each line of a FitNesse wiki page. May be
|
90
|
+
# an open File object, or an Array of strings.
|
91
|
+
#
|
92
|
+
# @return [Array]
|
93
|
+
# All Cucumber tables found in the given wiki page, or an empty array if
|
94
|
+
# no tables are found.
|
95
|
+
#
|
96
|
+
def fitnesse_to_features(wiki_page)
|
97
|
+
|
98
|
+
tables = [] # List of all tables parsed so far
|
99
|
+
current_table = [] # List of lines in the current table
|
100
|
+
in_table = false # Are we inside a table right now?
|
101
|
+
|
102
|
+
wiki_page.each do |line|
|
103
|
+
# Strip newline
|
104
|
+
line = line.strip
|
105
|
+
|
106
|
+
# Beginning of a new table?
|
107
|
+
if line =~ /\| *Table *: *Cuke *\| *$/
|
108
|
+
in_table = true
|
109
|
+
current_table = []
|
110
|
+
|
111
|
+
# Already in a table?
|
112
|
+
elsif in_table
|
113
|
+
# Append a new row to the current table, with pipes
|
114
|
+
# and leading/trailing whitespace removed
|
115
|
+
if line =~ /\| *(.*) *\| *$/
|
116
|
+
row = $1.split('|').collect { |cell| unescape(cell.strip) }
|
117
|
+
current_table << row
|
118
|
+
# No more rows; end this table and append to the results
|
119
|
+
else
|
120
|
+
in_table = false
|
121
|
+
tables << current_table
|
122
|
+
current_table = []
|
123
|
+
end
|
124
|
+
|
125
|
+
# Ignore all non-table lines in the content
|
126
|
+
else
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# If we're still inside a table, append it (this means that the last line
|
132
|
+
# of the table was the last line of the file, and there were no more
|
133
|
+
# lines after the table to terminate it)
|
134
|
+
if in_table
|
135
|
+
tables << current_table
|
136
|
+
end
|
137
|
+
|
138
|
+
return tables
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
module Cukable
|
146
|
+
# Main front-end for converting features, used by the `cuke2fit` script.
|
147
|
+
class Converter
|
148
|
+
|
149
|
+
include Cukable::Helper
|
150
|
+
include Cukable::Conversion
|
151
|
+
|
152
|
+
# Convert all .feature files in `features_path` to FitNesse wiki pages
|
153
|
+
# under `fitnesse_path`.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# features_to_fitnesse('features/account', 'FitNesseRoot/AccountTests')
|
157
|
+
#
|
158
|
+
# @param [String] features_path
|
159
|
+
# Directory where `.feature` files reside
|
160
|
+
# @param [String] fitnesse_path
|
161
|
+
# Directory within the FitNesse wiki hierarchy where converted features
|
162
|
+
# will be written as wiki pages. This must be the path of an existing page.
|
163
|
+
#
|
164
|
+
def features_to_fitnesse(features_path, fitnesse_path)
|
165
|
+
# Status messages to return
|
166
|
+
messages = []
|
167
|
+
|
168
|
+
# Ensure FitNesse directory already exists
|
169
|
+
if !File.directory?(fitnesse_path)
|
170
|
+
raise ArgumentError, "FitNesse path must be an existing directory."
|
171
|
+
end
|
172
|
+
|
173
|
+
# Create a root-level suite and SetUp page
|
174
|
+
root_path = File.join(fitnesse_path, wikify_path(features_path))
|
175
|
+
create_feature_root(root_path)
|
176
|
+
create_setup_page(root_path)
|
177
|
+
|
178
|
+
# Get all .feature files
|
179
|
+
features = Dir.glob(File.join(features_path, '**', '*feature'))
|
180
|
+
|
181
|
+
# Create a test page for each .feature file
|
182
|
+
features.each do |feature_path|
|
183
|
+
# Determine the appropriate wiki path name
|
184
|
+
wiki_path = File.join(fitnesse_path, wikify_path(feature_path))
|
185
|
+
# Fill ancestors of the wiki path with stubs for suites
|
186
|
+
create_suite_stubs(File.dirname(wiki_path))
|
187
|
+
# Convert the .feature to wikitext
|
188
|
+
content = feature_to_fitnesse(File.open(feature_path)).join("\n")
|
189
|
+
# Write the wikitext to a wiki page
|
190
|
+
create_wiki_page(wiki_path, content, 'test')
|
191
|
+
# Show user some status output
|
192
|
+
messages << "OK: #{feature_path} => #{wiki_path}"
|
193
|
+
end
|
194
|
+
return messages
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# Create a new wiki page at the given path, with the given content.
|
199
|
+
#
|
200
|
+
# @param [String] path
|
201
|
+
# Directory name where page should reside. Will be created if it
|
202
|
+
# does not already exist.
|
203
|
+
# @param [String] content
|
204
|
+
# Raw string content of the new page. May contain newlines.
|
205
|
+
# @param [String] type
|
206
|
+
# Type of page to write. May be 'normal', 'test', or 'suite'.
|
207
|
+
#
|
208
|
+
def create_wiki_page(path, content, type='normal')
|
209
|
+
FileUtils.mkdir_p(path)
|
210
|
+
# Write the content
|
211
|
+
File.open(File.join(path, 'content.txt'), 'w') do |file|
|
212
|
+
file.write(content)
|
213
|
+
end
|
214
|
+
# Write the properties.xml
|
215
|
+
File.open(File.join(path, 'properties.xml'), 'w') do |file|
|
216
|
+
file.puts '<?xml version="1.0"?>'
|
217
|
+
file.puts '<properties>'
|
218
|
+
file.puts ' <Edit>true</Edit>'
|
219
|
+
file.puts ' <Files>true</Files>'
|
220
|
+
file.puts ' <Properties>true</Properties>'
|
221
|
+
file.puts ' <RecentChanges>true</RecentChanges>'
|
222
|
+
file.puts ' <Refactor>true</Refactor>'
|
223
|
+
file.puts ' <Search>true</Search>'
|
224
|
+
if type == 'test'
|
225
|
+
file.puts ' <Test/>'
|
226
|
+
elsif type == 'suite'
|
227
|
+
file.puts ' <Suite/>'
|
228
|
+
end
|
229
|
+
file.puts ' <Versions>true</Versions>'
|
230
|
+
file.puts ' <WhereUsed>true</WhereUsed>'
|
231
|
+
file.puts '</properties>'
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# Create a stub `content.txt` file in the given directory, and all
|
237
|
+
# ancestor directories, if a `content.txt` does not already exist.
|
238
|
+
#
|
239
|
+
# @example
|
240
|
+
# create_suite_stubs('FitNesseRoot/PageOne/PageTwo/PageThree')
|
241
|
+
# # Creates these files, and their containing directories:
|
242
|
+
# # FitNesseRoot/PageOne/content.txt
|
243
|
+
# # FitNesseRoot/PageOne/PageTwo/content.txt
|
244
|
+
# # FitNesseRoot/PageOne/PageTwo/PageThree/content.txt
|
245
|
+
#
|
246
|
+
# @param [String] fitnesse_path
|
247
|
+
# Directory name of deepest level in the wiki hierarchy where
|
248
|
+
# you want content stubs to be created
|
249
|
+
#
|
250
|
+
def create_suite_stubs(fitnesse_path)
|
251
|
+
# Content string to put in each stub file
|
252
|
+
content = '!contents -R9 -p -f -h'
|
253
|
+
# Starting with `fitnesse_path`
|
254
|
+
path = fitnesse_path
|
255
|
+
# Until there are no more ancestor directories
|
256
|
+
while path != '.'
|
257
|
+
# If there is no content.txt file, create one
|
258
|
+
if !File.exists?(File.join(path, 'content.txt'))
|
259
|
+
create_wiki_page(path, content, 'suite')
|
260
|
+
end
|
261
|
+
# If there is no accelerator child, create one
|
262
|
+
if !File.directory?(File.join(path, 'AaaAccelerator'))
|
263
|
+
create_accelerator(path)
|
264
|
+
end
|
265
|
+
# Get the parent path
|
266
|
+
path = File.dirname(path)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
# Create a page at the top level of the converted features,
|
272
|
+
# containing variable definitions needed to invoke rubyslim
|
273
|
+
def create_feature_root(fitnesse_path)
|
274
|
+
FileUtils.mkdir_p(fitnesse_path)
|
275
|
+
content = [
|
276
|
+
'These variables must be defined for rubyslim to work:',
|
277
|
+
'!define TEST_SYSTEM {slim}',
|
278
|
+
'!define TEST_RUNNER {rubyslim}',
|
279
|
+
'!define COMMAND_PATTERN {rubyslim}',
|
280
|
+
'',
|
281
|
+
'Extra command-line arguments to pass to Cucumber:',
|
282
|
+
'!define CUCUMBER_ARGS {}',
|
283
|
+
'',
|
284
|
+
'!contents -R9 -p -f -h',
|
285
|
+
].join("\n")
|
286
|
+
create_wiki_page(fitnesse_path, content, 'suite')
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Create a SetUp page as a child of the given path.
|
291
|
+
def create_setup_page(fitnesse_path)
|
292
|
+
setup_path = File.join(fitnesse_path, 'SetUp')
|
293
|
+
FileUtils.mkdir_p(setup_path)
|
294
|
+
content = [
|
295
|
+
'!| import |',
|
296
|
+
'| Cukable |',
|
297
|
+
'',
|
298
|
+
'| script | Cuke |',
|
299
|
+
'| accelerate; | ${PAGE_PATH}.${PAGE_NAME} | ${CUCUMBER_ARGS} |',
|
300
|
+
].join("\n")
|
301
|
+
create_wiki_page(setup_path, content)
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
# Create an empty `AaaAccelerator` page under the given path.
|
306
|
+
def create_accelerator(fitnesse_path)
|
307
|
+
accel_path = File.join(fitnesse_path, 'AaaAccelerator')
|
308
|
+
FileUtils.mkdir_p(accel_path)
|
309
|
+
create_wiki_page(accel_path, '', 'test')
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|