cukable 0.1.1

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