gitnesse 0.1.3 → 0.12.5
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/README.md +15 -9
- data/bin/gitnesse +5 -3
- data/lib/gitnesse.rb +48 -247
- data/lib/gitnesse/configuration.rb +54 -0
- data/lib/gitnesse/dependencies.rb +38 -0
- data/lib/gitnesse/features.rb +38 -0
- data/lib/gitnesse/git_config.rb +39 -0
- data/lib/gitnesse/hooks.rb +50 -0
- data/lib/gitnesse/railtie.rb +2 -2
- data/lib/gitnesse/support/hook.rb +11 -0
- data/lib/gitnesse/tasks.rake +12 -6
- data/lib/gitnesse/version.rb +1 -1
- data/lib/gitnesse/wiki.rb +173 -0
- data/test/lib/gitnesse/build_page_content_test.rb +12 -3
- data/test/lib/gitnesse/commit_info_test.rb +8 -20
- data/test/lib/gitnesse/{config_to_hash_test.rb → configuration_to_hash_test.rb} +8 -7
- data/test/lib/gitnesse/custom_branch_test.rb +8 -9
- data/test/lib/gitnesse/dependencies_check_test.rb +60 -0
- data/test/lib/gitnesse/extract_features_test.rb +2 -2
- data/test/lib/gitnesse/{gather_features_test.rb → gather_test.rb} +6 -6
- data/test/lib/gitnesse/load_feature_files_into_wiki_test.rb +1 -1
- data/test/lib/gitnesse/read_git_config_test.rb +9 -11
- data/test/lib/gitnesse/strip_results_test.rb +52 -0
- data/test/lib/gitnesse/write_file_test.rb +18 -0
- metadata +21 -12
- data/test/lib/gitnesse/check_dependencies_test.rb +0 -45
- data/test/lib/gitnesse/write_feature_file_test.rb +0 -18
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Gitnesse
|
2
2
|
|
3
|
-
Gitnesse is
|
4
|
-
|
5
|
-
|
3
|
+
Gitnesse is an acceptance testing tool.
|
4
|
+
|
5
|
+
It enables a project to store Cucumber feature stories in a git-based wiki, test them against your code, and then update the wiki with the latest test results.
|
6
|
+
|
7
|
+
Conceptually influenced by Fitnesse http://fitnesse.org/ thank you Uncle Bob!
|
6
8
|
|
7
9
|
## Installation
|
8
10
|
|
@@ -21,8 +23,10 @@ Or install it yourself as:
|
|
21
23
|
Create a `gitnesse.rb` file somewhere in your project, and add something like
|
22
24
|
the following to it:
|
23
25
|
|
24
|
-
Gitnesse.
|
25
|
-
repository_url "git@github.com:hybridgroup/gitnesse-demo.wiki"
|
26
|
+
Gitnesse.configure do |config|
|
27
|
+
config.repository_url = "git@github.com:hybridgroup/gitnesse-demo.wiki"
|
28
|
+
config.annotate_results = true
|
29
|
+
config.info = "Bob Martin's development laptop"
|
26
30
|
end
|
27
31
|
|
28
32
|
## rake tasks
|
@@ -31,6 +35,7 @@ the following to it:
|
|
31
35
|
$ rake gitnesse:push
|
32
36
|
$ rake gitnesse:run
|
33
37
|
$ rake gitnesse:info
|
38
|
+
$ rake gitnesse:push_results
|
34
39
|
|
35
40
|
## Usage In Rails 3
|
36
41
|
|
@@ -48,14 +53,15 @@ There is an example application using Sinatra located here: [https://github.com/
|
|
48
53
|
|
49
54
|
## Other Usage
|
50
55
|
|
51
|
-
Want to use plain old Gitnesse? There is an executable
|
56
|
+
Want to use plain old Gitnesse? There is an executable, if you're allergic to rake tasks:
|
57
|
+
|
58
|
+
$ gitnesse
|
52
59
|
|
53
|
-
$ GITNESSE_CONFIG='./gitnesse_config.rb' gitnesse
|
54
60
|
|
55
61
|
## TODO
|
56
62
|
|
57
|
-
|
58
|
-
|
63
|
+
- pluggable feature runners, so can be used with Spinach, Cucumber-JS, or ?
|
64
|
+
- standalone server so end users to run tests and see live results, just like Fitnesse
|
59
65
|
|
60
66
|
## Contributing
|
61
67
|
|
data/bin/gitnesse
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'gitnesse'
|
5
5
|
|
6
|
-
Gitnesse.
|
6
|
+
Gitnesse::Configuration.load_using_search
|
7
7
|
|
8
8
|
if ARGV[0].nil?
|
9
9
|
Gitnesse.run
|
@@ -11,7 +11,7 @@ end
|
|
11
11
|
|
12
12
|
def print_help
|
13
13
|
puts "Gitnesse commands:"
|
14
|
-
puts " run:
|
14
|
+
puts " run: pull remote features from git-based wiki to local, and run Cucumber"
|
15
15
|
puts " push: push local features to git-based wiki"
|
16
16
|
puts " pull: pull remote features from git-based wiki to local"
|
17
17
|
puts " info: print current Gitnesse configuration"
|
@@ -25,7 +25,9 @@ when "pull"
|
|
25
25
|
when "run"
|
26
26
|
Gitnesse.run
|
27
27
|
when "info"
|
28
|
-
puts Gitnesse.
|
28
|
+
puts Gitnesse.configuration.to_yaml
|
29
|
+
when "push_results"
|
30
|
+
Gitnesse.push_results
|
29
31
|
when "help"
|
30
32
|
print_help
|
31
33
|
else
|
data/lib/gitnesse.rb
CHANGED
@@ -2,77 +2,27 @@ require 'bundler/setup'
|
|
2
2
|
require 'gollum'
|
3
3
|
require 'fileutils'
|
4
4
|
require 'tmpdir'
|
5
|
+
require 'gitnesse/configuration'
|
6
|
+
require 'gitnesse/git_config'
|
7
|
+
require 'gitnesse/dependencies'
|
8
|
+
require 'gitnesse/features'
|
9
|
+
require 'gitnesse/hooks'
|
10
|
+
require 'gitnesse/wiki'
|
5
11
|
require 'gitnesse/railtie' if defined?(Rails)
|
6
12
|
|
7
13
|
# core module
|
8
14
|
module Gitnesse
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Public: Set url of the git-based wiki repo containing features.
|
13
|
-
#
|
14
|
-
# repository_url - A String containing your repo's url.
|
15
|
-
#
|
16
|
-
# Example:
|
17
|
-
#
|
18
|
-
# Gitnesse.config do
|
19
|
-
# repository_url "git@github.com:luishurtado/gitnesse-wiki.wiki"
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
def self.repository_url(repository_url = false)
|
23
|
-
if repository_url == false
|
24
|
-
unless defined?(@@repository_url)
|
25
|
-
puts "---"
|
26
|
-
puts "No repository_url has been defined for Gitnesse."
|
27
|
-
puts "Add a gitnesse.rb file to your project and use it to configure Gitnesse."
|
28
|
-
puts "For more details, check out gitnesse.com"
|
29
|
-
auts "---"
|
30
|
-
exit 1
|
31
|
-
end
|
32
|
-
return @@repository_url
|
33
|
-
else
|
34
|
-
@@repository_url = repository_url
|
35
|
-
end
|
15
|
+
class << self
|
16
|
+
attr_accessor :configuration
|
17
|
+
attr_accessor :commit_info
|
36
18
|
end
|
37
19
|
|
38
|
-
|
39
|
-
#
|
40
|
-
# branch - A String containing which branch of your repo to use.
|
41
|
-
#
|
42
|
-
# Example:
|
43
|
-
#
|
44
|
-
# Gitnesse.config do
|
45
|
-
# branch "master"
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
def self.branch(branch = false)
|
49
|
-
if branch == false
|
50
|
-
@@branch ||= "master"
|
51
|
-
else
|
52
|
-
@@branch = branch
|
53
|
-
end
|
54
|
-
end
|
20
|
+
self.configuration ||= Configuration.new
|
55
21
|
|
56
|
-
|
57
|
-
#
|
58
|
-
# target_directory - A String containing which directory to use.
|
59
|
-
#
|
60
|
-
# Example:
|
61
|
-
#
|
62
|
-
# Gitnesse.config do
|
63
|
-
# target_directory "features"
|
64
|
-
# end
|
65
|
-
#
|
66
|
-
def self.target_directory(target_directory = false)
|
67
|
-
if target_directory == false
|
68
|
-
@@target_directory ||= File.join(Dir.pwd, 'features')
|
69
|
-
else
|
70
|
-
@@target_directory = target_directory
|
71
|
-
end
|
72
|
-
end
|
22
|
+
extend self
|
73
23
|
|
74
|
-
def self.
|
75
|
-
|
24
|
+
def self.configure
|
25
|
+
yield(configuration)
|
76
26
|
end
|
77
27
|
|
78
28
|
# -- all methods after this are module functions --
|
@@ -80,44 +30,52 @@ module Gitnesse
|
|
80
30
|
|
81
31
|
def run
|
82
32
|
if pull
|
33
|
+
Hooks.create
|
34
|
+
puts "Now going to run cucumber..."
|
35
|
+
exec("cucumber #{Gitnesse.configuration.target_directory}/*.feature")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def push_results
|
40
|
+
if push
|
41
|
+
Hooks.create
|
83
42
|
puts "Now going to run cucumber..."
|
84
|
-
exec("cucumber #{Gitnesse.target_directory}/*.feature")
|
43
|
+
exec("cucumber #{Gitnesse.configuration.target_directory}/*.feature")
|
85
44
|
end
|
86
45
|
end
|
87
46
|
|
88
47
|
# pull features from git wiki, and sync up with features dir
|
89
48
|
def pull
|
90
|
-
|
91
|
-
ensure_cucumber_available
|
92
|
-
ensure_repository
|
49
|
+
Dependencies.check
|
93
50
|
|
94
|
-
puts "Pulling features into #{Gitnesse.target_directory} from #{Gitnesse.repository_url}..."
|
51
|
+
puts "Pulling features into #{Gitnesse.configuration.target_directory} from #{Gitnesse.configuration.repository_url}..."
|
95
52
|
Dir.mktmpdir do |tmp_dir|
|
96
53
|
if clone_feature_repo(tmp_dir)
|
97
|
-
FileUtils.mkdir(Gitnesse.target_directory) unless File.exists?(Gitnesse.target_directory)
|
98
|
-
|
99
|
-
wiki_pages =
|
100
|
-
wiki_pages.each do |
|
101
|
-
|
102
|
-
|
103
|
-
|
54
|
+
FileUtils.mkdir(Gitnesse.configuration.target_directory) unless File.exists?(Gitnesse.configuration.target_directory)
|
55
|
+
|
56
|
+
wiki_pages = Wiki.new(tmp_dir).pages
|
57
|
+
wiki_pages.each do |page|
|
58
|
+
name = page.name.gsub('.feature', '')
|
59
|
+
filename = "#{Gitnesse.configuration.target_directory}/#{name}.feature"
|
60
|
+
features = Wiki.extract_features(page)
|
61
|
+
Features.write_file(filename, features) unless features.empty?
|
104
62
|
end
|
105
63
|
end
|
106
64
|
end
|
107
|
-
puts "
|
65
|
+
puts " Done pulling features."
|
66
|
+
true
|
108
67
|
end
|
109
68
|
|
110
69
|
# push features back up to git wiki from features directory
|
111
70
|
def push
|
112
|
-
|
113
|
-
|
114
|
-
ensure_repository
|
115
|
-
commit_info
|
71
|
+
Dependencies.check
|
72
|
+
generate_commit_info
|
116
73
|
|
117
|
-
puts "Pushing features from #{Gitnesse.target_directory} to #{Gitnesse.repository_url}..."
|
74
|
+
puts "Pushing features from #{Gitnesse.configuration.target_directory} to #{Gitnesse.configuration.repository_url}..."
|
118
75
|
Dir.mktmpdir do |tmp_dir|
|
119
76
|
if clone_feature_repo(tmp_dir)
|
120
|
-
|
77
|
+
feature_files = Dir.glob("#{Gitnesse.configuration.target_directory}/*.feature")
|
78
|
+
Wiki.new(tmp_dir).load_feature_files(feature_files)
|
121
79
|
|
122
80
|
# push the changes to the remote git
|
123
81
|
Dir.chdir(tmp_dir) do
|
@@ -125,178 +83,21 @@ module Gitnesse
|
|
125
83
|
end
|
126
84
|
end
|
127
85
|
end
|
128
|
-
puts "
|
129
|
-
|
130
|
-
|
131
|
-
def load_feature_files_into_wiki(tmp_dir)
|
132
|
-
wiki = Gollum::Wiki.new(tmp_dir)
|
133
|
-
feature_files = Dir.glob("#{Gitnesse.target_directory}/*.feature")
|
134
|
-
|
135
|
-
feature_files.each do |feature_file|
|
136
|
-
feature_name = File.basename(feature_file, ".feature")
|
137
|
-
feature_content = File.read(feature_file)
|
138
|
-
wiki_page = wiki.page(feature_name)
|
139
|
-
wiki_page ||= wiki.page("#{feature_name}.feature")
|
140
|
-
|
141
|
-
if wiki_page
|
142
|
-
update_wiki_page(wiki, wiki_page, feature_name, feature_content)
|
143
|
-
else
|
144
|
-
create_wiki_page(wiki, feature_name, feature_content)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def create_wiki_page(wiki, page_name, feature_content)
|
150
|
-
new_page_content = build_page_content(feature_content)
|
151
|
-
|
152
|
-
wiki.write_page(page_name, :markdown, new_page_content, commit_info)
|
153
|
-
puts "==== Created page: #{page_name} ==="
|
154
|
-
end
|
155
|
-
|
156
|
-
def update_wiki_page(wiki, wiki_page, page_name, feature_content)
|
157
|
-
wiki_page_content = wiki_page.raw_data
|
158
|
-
new_page_content = build_page_content(feature_content, wiki_page_content)
|
159
|
-
|
160
|
-
if new_page_content == wiki_page_content
|
161
|
-
puts "=== Page #{page_name} didn't change ==="
|
162
|
-
else
|
163
|
-
wiki.update_page(wiki_page, page_name, :markdown, new_page_content, commit_info)
|
164
|
-
puts "==== Updated page: #{page_name} ==="
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def build_page_content(feature_content, wiki_page_content = nil)
|
169
|
-
return "```gherkin\n#{feature_content}\n```" if wiki_page_content.nil? || wiki_page_content.empty?
|
170
|
-
features = extract_features(wiki_page_content)
|
171
|
-
|
172
|
-
# replace the first feature found in the wiki page
|
173
|
-
_, old_feature_content = features.shift
|
174
|
-
wiki_page_content.sub(old_feature_content, feature_content)
|
175
|
-
end
|
176
|
-
|
177
|
-
# look thru wiki page for features
|
178
|
-
def extract_features(data)
|
179
|
-
features = {}
|
180
|
-
|
181
|
-
if match_result = data.match(/\u0060{3}gherkin(.+)\u0060{3}/m)
|
182
|
-
captures = match_result.captures
|
183
|
-
|
184
|
-
# create hash with feature name as key and feature text as value
|
185
|
-
captures.each do |capture|
|
186
|
-
feature_definition_at = capture.index('Feature:')
|
187
|
-
feature_text = capture[feature_definition_at,capture.size-1]
|
188
|
-
feature_lines = feature_text.split("\n")
|
189
|
-
feature_definition = feature_lines.grep(/^Feature:/).first
|
190
|
-
|
191
|
-
if feature_definition
|
192
|
-
feature_name = feature_definition.split(":").last.strip.gsub(" ","-").downcase
|
193
|
-
features[feature_name] = feature_text
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
features
|
86
|
+
puts " Done pushing features."
|
87
|
+
true
|
199
88
|
end
|
200
89
|
|
201
90
|
def clone_feature_repo(dir)
|
202
|
-
output = `git clone #{Gitnesse.repository_url} #{dir} 2>&1`
|
91
|
+
output = `git clone #{Gitnesse.configuration.repository_url} #{dir} 2>&1`
|
203
92
|
puts output
|
204
93
|
$?.success?
|
205
94
|
end
|
206
95
|
|
207
|
-
def
|
208
|
-
|
209
|
-
user_name =
|
210
|
-
email =
|
211
|
-
|
212
|
-
raise "Can't read git's user.email config" if email.nil? || email.empty?
|
213
|
-
|
214
|
-
{:name => user_name, :email => email, :message => "Update features with Gitnesse"}
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def commit_info=(commit_info)
|
219
|
-
@commit_info = commit_info
|
220
|
-
end
|
221
|
-
|
222
|
-
def read_git_config(config_name)
|
223
|
-
config_value = ""
|
224
|
-
config_value = `git config --get #{config_name}`
|
225
|
-
config_value = `git config --get --global #{config_name}` unless $?.success?
|
226
|
-
config_value.strip
|
227
|
-
end
|
228
|
-
|
229
|
-
# we are going to support only one feature per page
|
230
|
-
def gather_features(page_features)
|
231
|
-
return "" if page_features.nil? or page_features.empty?
|
232
|
-
|
233
|
-
features = ''
|
234
|
-
feature_name, feature_content = page_features.shift
|
235
|
-
puts "============================== Pulling Feature: #{feature_name} =============================="
|
236
|
-
features = features + feature_content
|
237
|
-
|
238
|
-
page_features.each do |_feature_name, _feature_content|
|
239
|
-
puts "============================== WARNING! Discarded Feature: #{_feature_name} =============================="
|
240
|
-
puts _feature_content
|
241
|
-
end
|
242
|
-
|
243
|
-
features
|
244
|
-
end
|
245
|
-
|
246
|
-
def write_feature_file(page_name, page_features)
|
247
|
-
File.open("#{Gitnesse.target_directory}/#{page_name}.feature","w") {|f| f.write(gather_features(page_features)) }
|
248
|
-
end
|
249
|
-
|
250
|
-
def ensure_git_available
|
251
|
-
raise "git not found or not working." unless Kernel.system("git --version")
|
252
|
-
end
|
253
|
-
|
254
|
-
def ensure_cucumber_available
|
255
|
-
raise "cucumber not found or not working." unless Kernel.system("cucumber --version")
|
256
|
-
end
|
257
|
-
|
258
|
-
def ensure_repository
|
259
|
-
raise "You must select a repository_url to run Gitnesse." if Gitnesse.repository_url.nil?
|
260
|
-
end
|
261
|
-
|
262
|
-
def load_config
|
263
|
-
load(ENV['GITNESSE_CONFIG']) and return if ENV['GITNESSE_CONFIG']
|
264
|
-
|
265
|
-
possible_config_files = Dir.glob(File.join("**", "gitnesse.rb"))
|
266
|
-
|
267
|
-
files_with_config = possible_config_files.select do |file_name|
|
268
|
-
if FileUtils.compare_file(__FILE__, file_name)
|
269
|
-
false
|
270
|
-
elsif File.fnmatch("vendor/**/*.rb", file_name)
|
271
|
-
false
|
272
|
-
else
|
273
|
-
file_content = File.read(file_name)
|
274
|
-
file_content.match("Gitnesse.config")
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
case files_with_config.length
|
279
|
-
when 0
|
280
|
-
raise "Can't find a gitnesse.rb file with Gitnesse configuration."
|
281
|
-
when 1
|
282
|
-
load(File.absolute_path(files_with_config.first))
|
283
|
-
else
|
284
|
-
raise "Several config files found: #{files_with_config.join(", ")}"
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def config_to_hash
|
289
|
-
{ "repository_url" => Gitnesse.repository_url,
|
290
|
-
"branch" => Gitnesse.branch,
|
291
|
-
"target_directory" => Gitnesse.target_directory }
|
292
|
-
end
|
293
|
-
|
294
|
-
def method_missing(sym, *args, &block)
|
295
|
-
unless ["to_str", "to_ary"].contains?(sym)
|
296
|
-
raise "Invalid variable name for Gitnesse configuration.
|
297
|
-
Allowed variables are repository_url, branch, and target_directory."
|
298
|
-
else
|
299
|
-
super
|
96
|
+
def generate_commit_info
|
97
|
+
self.commit_info ||= begin
|
98
|
+
user_name = GitConfig.read("user.name")
|
99
|
+
email = GitConfig.read("user.email")
|
100
|
+
{ :name => user_name, :email => email, :message => "Update features with Gitnesse" }
|
300
101
|
end
|
301
102
|
end
|
302
103
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Gitnesse
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :repository_url
|
4
|
+
attr_accessor :branch
|
5
|
+
attr_accessor :target_directory
|
6
|
+
attr_accessor :annotate_results
|
7
|
+
attr_accessor :info
|
8
|
+
|
9
|
+
class NoConfigFileError < StandardError ; end
|
10
|
+
class MultipleConfigFileError < StandardError ; end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@branch = 'master'
|
14
|
+
@target_directory = File.join(Dir.pwd, 'features')
|
15
|
+
@annotate_results = false
|
16
|
+
@info = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Returns the current Gitnesse configuration as a Hash
|
20
|
+
#
|
21
|
+
# Returns a hash containing the current Gitnesse configuration
|
22
|
+
def to_hash
|
23
|
+
{ 'repository_url' => @repository_url,
|
24
|
+
'branch' => @branch,
|
25
|
+
'target_directory' => @target_directory }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load_using_search
|
29
|
+
load(ENV['GITNESSE_CONFIG']) and return if ENV['GITNESSE_CONFIG']
|
30
|
+
|
31
|
+
possible_config_files = Dir.glob(File.join("**", "gitnesse.rb"))
|
32
|
+
|
33
|
+
files_with_config = possible_config_files.select do |file_name|
|
34
|
+
if FileUtils.compare_file(__FILE__, file_name)
|
35
|
+
false
|
36
|
+
elsif File.fnmatch("vendor/**/*.rb", file_name)
|
37
|
+
false
|
38
|
+
else
|
39
|
+
file_content = File.read(file_name)
|
40
|
+
file_content.match("Gitnesse.configure")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
case files_with_config.length
|
45
|
+
when 0
|
46
|
+
raise NoConfigFileError, "Can't find a gitnesse.rb file with Gitnesse configuration."
|
47
|
+
when 1
|
48
|
+
load(File.absolute_path(files_with_config.first))
|
49
|
+
else
|
50
|
+
raise MultipleConfigFileError, "Several config files found: #{files_with_config.join(", ")}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|