cukable 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|