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.
@@ -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,2 @@
1
+ module Cukable
2
+ end
@@ -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
+