pluginfactory 1.0.2 → 1.0.3

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/rake/svn.rb ADDED
@@ -0,0 +1,461 @@
1
+ #
2
+ # Subversion Rake Tasks
3
+ # $Id: svn.rb 25 2008-08-09 01:19:41Z deveiant $
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+
9
+
10
+ require 'pp'
11
+ require 'yaml'
12
+ require 'date'
13
+
14
+ # Strftime format for tags/releases
15
+ TAG_TIMESTAMP_FORMAT = '%Y%m%d-%H%M%S'
16
+ TAG_TIMESTAMP_PATTERN = /\d{4}\d{2}\d{2}-\d{6}/
17
+
18
+ RELEASE_VERSION_PATTERN = /\d+\.\d+\.\d+/
19
+
20
+ DEFAULT_EDITOR = 'vi'
21
+ DEFAULT_KEYWORDS = %w[Date Rev Author URL Id]
22
+ KEYWORDED_FILEDIRS = %w[applets bin etc lib misc]
23
+ KEYWORDED_FILEPATTERN = /^(?:Rakefile|.*\.(?:rb|js|html|template))$/i
24
+
25
+ COMMIT_MSG_FILE = 'commit-msg.txt'
26
+
27
+ SVN_TRUNK_DIR = 'trunk' unless defined?( SVN_TRUNK_DIR )
28
+ SVN_RELEASES_DIR = 'branches' unless defined?( SVN_RELEASES_DIR )
29
+ SVN_BRANCHES_DIR = 'branches' unless defined?( SVN_BRANCHES_DIR )
30
+ SVN_TAGS_DIR = 'tags' unless defined?( SVN_TAGS_DIR )
31
+
32
+ FILE_INDENT = " " * 12
33
+ LOG_INDENT = " " * 3
34
+
35
+
36
+
37
+ ###
38
+ ### Subversion-specific Helpers
39
+ ###
40
+
41
+ ### Return a new tag for the given time
42
+ def make_new_tag( time=Time.now )
43
+ return time.strftime( TAG_TIMESTAMP_FORMAT )
44
+ end
45
+
46
+
47
+ ### Get the subversion information for the current working directory as
48
+ ### a hash.
49
+ def get_svn_info( dir='.' )
50
+ return {} unless File.directory?( File.join(dir, '.svn') )
51
+ info = IO.read( '|-' ) or exec 'svn', 'info', dir
52
+ return YAML.load( info ) # 'svn info' outputs valid YAML! Yay!
53
+ end
54
+
55
+
56
+ ### Get a list of the objects registered with subversion under the specified directory and
57
+ ### return them as an Array of Pathame objects.
58
+ def get_svn_filelist( dir='.' )
59
+ list = IO.read( '|-' ) or exec 'svn', 'st', '-v', '--ignore-externals', dir
60
+
61
+ # Split into lines, filter out the unknowns, and grab the filenames as Pathnames
62
+ # :FIXME: This will break if we ever put in a file with spaces in its name. This
63
+ # will likely be the least of our worries if we do so, however, so it's not worth
64
+ # the additional complexity to make it handle that case. If we do need that, there's
65
+ # always the --xml output for 'svn st'...
66
+ return list.split( $/ ).
67
+ reject {|line| line =~ /^\?/ }.
68
+ collect {|fn| Pathname(fn[/\S+$/]) }
69
+ end
70
+
71
+ ### Return the URL to the repository root for the specified +dir+.
72
+ def get_svn_repo_root( dir='.' )
73
+ info = get_svn_info( dir )
74
+ return info['Repository Root']
75
+ end
76
+
77
+
78
+ ### Return the Subversion URL to the given +dir+.
79
+ def get_svn_url( dir='.' )
80
+ info = get_svn_info( dir )
81
+ return info['URL']
82
+ end
83
+
84
+
85
+ ### Return the path of the specified +dir+ under the svn root of the
86
+ ### checkout.
87
+ def get_svn_path( dir='.' )
88
+ root = get_svn_repo_root( dir )
89
+ url = get_svn_url( dir )
90
+
91
+ return url.sub( root + '/', '' )
92
+ end
93
+
94
+
95
+ ### Return the keywords for the specified array of +files+ as a Hash keyed by filename.
96
+ def get_svn_keyword_map( files )
97
+ cmd = ['svn', 'pg', 'svn:keywords', *files]
98
+
99
+ # trace "Executing: svn pg svn:keywords " + files.join(' ')
100
+ output = IO.read( '|-' ) or exec( 'svn', 'pg', 'svn:keywords', *files )
101
+
102
+ kwmap = {}
103
+ output.split( "\n" ).each do |line|
104
+ next if line !~ /\s+-\s+/
105
+ path, keywords = line.split( /\s+-\s+/, 2 )
106
+ kwmap[ path ] = keywords.split
107
+ end
108
+
109
+ return kwmap
110
+ end
111
+
112
+
113
+ ### Return the latest revision number of the specified +dir+ as an Integer.
114
+ def get_svn_rev( dir='.' )
115
+ info = get_svn_info( dir )
116
+ return info['Revision']
117
+ end
118
+
119
+
120
+ ### Return the latest revision number of the specified +dir+ as an Integer.
121
+ def get_last_changed_rev( dir='.' )
122
+ info = get_svn_info( dir )
123
+ return info['Last Changed Rev']
124
+ end
125
+
126
+
127
+ ### Return a list of the entries at the specified Subversion url. If
128
+ ### no +url+ is specified, it will default to the list in the URL
129
+ ### corresponding to the current working directory.
130
+ def svn_ls( url=nil )
131
+ url ||= get_svn_url()
132
+ list = IO.read( '|-' ) or exec 'svn', 'ls', url
133
+
134
+ trace 'svn ls of %s: %p' % [url, list] if $trace
135
+
136
+ return [] if list.nil? || list.empty?
137
+ return list.split( $INPUT_RECORD_SEPARATOR )
138
+ end
139
+
140
+
141
+ ### Return the URL of the latest timestamp in the tags directory.
142
+ def get_latest_svn_timestamp_tag
143
+ rooturl = get_svn_repo_root()
144
+ tagsurl = rooturl + "/#{SVN_TAGS_DIR}"
145
+
146
+ tags = svn_ls( tagsurl ).grep( TAG_TIMESTAMP_PATTERN ).sort
147
+ return nil if tags.nil? || tags.empty?
148
+ return tagsurl + '/' + tags.last
149
+ end
150
+
151
+
152
+ ### Get a subversion diff of the specified targets and return it. If no targets are
153
+ ### specified, the current directory will be diffed instead.
154
+ def get_svn_diff( *targets )
155
+ targets << BASEDIR if targets.empty?
156
+ trace "Getting svn diff for targets: %p" % [targets]
157
+ log = IO.read( '|-' ) or exec 'svn', 'diff', *(targets.flatten)
158
+
159
+ return log
160
+ end
161
+
162
+
163
+ ### Return the URL of the latest timestamp in the tags directory.
164
+ def get_latest_release_tag
165
+ rooturl = get_svn_repo_root()
166
+ releaseurl = rooturl + "/#{SVN_RELEASES_DIR}"
167
+
168
+ tags = svn_ls( releaseurl ).grep( RELEASE_VERSION_PATTERN ).sort_by do |tag|
169
+ tag[RELEASE_VERSION_PATTERN].split('.').collect {|i| Integer(i) }
170
+ end
171
+ return nil if tags.empty?
172
+
173
+ return releaseurl + '/' + tags.last
174
+ end
175
+
176
+
177
+ ### Extract a diff from the specified subversion working +dir+ and return it.
178
+ def make_svn_commit_log( dir='.' )
179
+ diff = IO.read( '|-' ) or exec 'svn', 'diff'
180
+ fail "No differences." if diff.empty?
181
+
182
+ return diff
183
+ end
184
+
185
+
186
+ ### Extract the svn log from the specified subversion working +dir+,
187
+ ### starting from rev +start+ and ending with rev +finish+, and return it.
188
+ def make_svn_log( dir='.', start='PREV', finish='HEAD' )
189
+ trace "svn log -r#{start}:#{finish} #{dir}"
190
+ log = IO.read( '|-' ) or exec 'svn', 'log', "-r#{start}:#{finish}", dir
191
+ fail "No log between #{start} and #{finish}." if log.empty?
192
+
193
+ return log
194
+ end
195
+
196
+
197
+ ### Extract the verbose XML svn log from the specified subversion working +dir+,
198
+ ### starting from rev +start+ and ending with rev +finish+, and return it.
199
+ def make_xml_svn_log( dir='.', start='PREV', finish='HEAD' )
200
+ trace "svn log --xml --verbose -r#{start}:#{finish} #{dir}"
201
+ log = IO.read( '|-' ) or exec 'svn', 'log', '--verbose', '--xml', "-r#{start}:#{finish}", dir
202
+ fail "No log between #{start} and #{finish}." if log.empty?
203
+
204
+ return log
205
+ end
206
+
207
+
208
+ ### Create a changelog from the subversion log of the specified +dir+ and return it.
209
+ def make_svn_changelog( dir='.' )
210
+ require 'xml/libxml'
211
+
212
+ changelog = ''
213
+ path_prefix = '/' + get_svn_path( dir ) + '/'
214
+
215
+ xmllog = make_xml_svn_log( dir, 0 )
216
+
217
+ parser = XML::Parser.string( xmllog )
218
+ root = parser.parse.root
219
+ root.find( '//log/logentry' ).to_a.reverse.each do |entry|
220
+ trace "Making a changelog entry for r%s" % [ entry['revision'] ]
221
+
222
+ added = []
223
+ deleted = []
224
+ changed = []
225
+
226
+ entry.find( 'paths/path').each do |path|
227
+ pathname = path.content
228
+ pathname.sub!( path_prefix , '' ) if pathname.count('/') > 1
229
+
230
+ case path['action']
231
+ when 'A', 'R'
232
+ if path['copyfrom-path']
233
+ verb = path['action'] == 'A' ? 'renamed' : 'copied'
234
+ added << "%s\n#{FILE_INDENT}-> #{verb} from %s@r%s" % [
235
+ pathname,
236
+ path['copyfrom-path'],
237
+ path['copyfrom-rev'],
238
+ ]
239
+ else
240
+ added << "%s (new)" % [ pathname ]
241
+ end
242
+
243
+ when 'M'
244
+ changed << pathname
245
+
246
+ when 'D'
247
+ deleted << pathname
248
+
249
+ else
250
+ log "Unknown action %p in rev %d" % [ path['action'], entry['revision'] ]
251
+ end
252
+
253
+ end
254
+
255
+ date = Time.parse( entry.find_first('date').content )
256
+
257
+ # cvs2svn doesn't set 'author'
258
+ author = 'unknown'
259
+ if entry.find_first( 'author' )
260
+ author = entry.find_first( 'author' ).content
261
+ end
262
+
263
+ msg = entry.find_first( 'msg' ).content
264
+ rev = entry['revision']
265
+
266
+ changelog << "-- #{date.rfc2822} by #{author} (r#{rev}) -----\n"
267
+ changelog << " Added: " << humanize_file_list(added) << "\n" unless added.empty?
268
+ changelog << " Changed: " << humanize_file_list(changed) << "\n" unless changed.empty?
269
+ changelog << " Deleted: " << humanize_file_list(deleted) << "\n" unless deleted.empty?
270
+ changelog << "\n"
271
+
272
+ indent = msg[/^(\s*)/] + LOG_INDENT
273
+
274
+ changelog << indent << msg.strip.gsub(/\n\s*/m, "\n#{indent}")
275
+ changelog << "\n\n\n"
276
+ end
277
+
278
+ return changelog
279
+ end
280
+
281
+
282
+ ### Returns a human-scannable file list by joining and truncating the list if it's too long.
283
+ def humanize_file_list( list )
284
+ listtext = list[0..5].join( "\n#{FILE_INDENT}" )
285
+ if list.length > 5
286
+ listtext << " (and %d other/s)" % [ list.length - 5 ]
287
+ end
288
+
289
+ return listtext
290
+ end
291
+
292
+
293
+
294
+ ###
295
+ ### Tasks
296
+ ###
297
+
298
+ desc "Subversion tasks"
299
+ namespace :svn do
300
+
301
+ desc "Copy the HEAD revision of the current #{SVN_TRUNK_DIR}/ to #{SVN_TAGS_DIR}/ with a " +
302
+ "current timestamp."
303
+ task :tag do
304
+ svninfo = get_svn_info()
305
+ tag = make_new_tag()
306
+ svntrunk = svninfo['Repository Root'] + "/#{SVN_TRUNK_DIR}"
307
+ svntagdir = svninfo['Repository Root'] + "/#{SVN_TAGS_DIR}"
308
+ svntag = svntagdir + '/' + tag
309
+
310
+ desc = "Tagging trunk as #{svntag}"
311
+ ask_for_confirmation( desc ) do
312
+ msg = prompt_with_default( "Commit log: ", "Tagging for code push" )
313
+ run 'svn', 'cp', '-m', msg, svntrunk, svntag
314
+ end
315
+ end
316
+
317
+
318
+ desc "Copy the most recent tag to #{SVN_RELEASES_DIR}/#{PKG_VERSION}"
319
+ task :release do
320
+ last_tag = get_latest_svn_timestamp_tag()
321
+ svninfo = get_svn_info()
322
+ svnroot = svninfo['Repository Root']
323
+ svntrunk = svnroot + "/#{SVN_TRUNK_DIR}"
324
+ svnrel = svnroot + "/#{SVN_RELEASES_DIR}"
325
+ release = PKG_VERSION
326
+ svnrelease = svnrel + '/' + release
327
+
328
+ topdirs = svn_ls( svnroot ).collect {|dir| dir.chomp('/') }
329
+ unless topdirs.include?( SVN_RELEASES_DIR )
330
+ trace "Top directories (%p) does not include %p" %
331
+ [ topdirs, SVN_RELEASES_DIR ]
332
+ log "Releases path #{svnrel} does not exist."
333
+ ask_for_confirmation( "To continue I'll need to create it." ) do
334
+ run 'svn', 'mkdir', svnrel, '-m', 'Creating releases/ directory'
335
+ end
336
+ else
337
+ trace "Found release dir #{SVN_RELEASES_DIR} in the top directories %p" %
338
+ [ topdirs ]
339
+ end
340
+
341
+ releases = svn_ls( svnrel ).collect {|name| name.sub(%r{/$}, '') }
342
+ trace "Releases: %p" % [releases]
343
+ if releases.include?( release )
344
+ error "Version #{release} already has a branch (#{svnrelease}). Did you mean " +
345
+ "to increment the version in #{VERSION_FILE}?"
346
+ fail
347
+ else
348
+ trace "No #{release} version currently exists"
349
+ end
350
+
351
+ desc = "Tagging trunk as #{svnrelease}..."
352
+ ask_for_confirmation( desc ) do
353
+ msg = prompt_with_default( "Commit log: ", "Branching for release" )
354
+ run 'svn', 'cp', '-m', msg, svntrunk, svnrelease
355
+ end
356
+ end
357
+
358
+ ### Task for debugging the #get_target_args helper
359
+ task :show_targets do
360
+ $stdout.puts "Targets from ARGV (%p): %p" % [ARGV, get_target_args()]
361
+ end
362
+
363
+
364
+ desc "Generate a commit log"
365
+ task :commitlog => [COMMIT_MSG_FILE]
366
+
367
+ desc "Show the (pre-edited) commit log for the current directory"
368
+ task :show_commitlog do
369
+ puts make_svn_commit_log()
370
+ end
371
+
372
+
373
+ file COMMIT_MSG_FILE do
374
+ diff = make_svn_commit_log()
375
+
376
+ File.open( COMMIT_MSG_FILE, File::WRONLY|File::EXCL|File::CREAT ) do |fh|
377
+ fh.print( diff )
378
+ end
379
+
380
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
381
+ system editor, COMMIT_MSG_FILE
382
+ unless $?.success?
383
+ fail "Editor exited uncleanly."
384
+ end
385
+ end
386
+
387
+
388
+ desc "Update from Subversion"
389
+ task :update do
390
+ run 'svn', 'up', '--ignore-externals'
391
+ end
392
+
393
+
394
+ desc "Check in all the changes in your current working copy"
395
+ task :checkin => ['svn:update', 'test', 'svn:fix_keywords', COMMIT_MSG_FILE] do
396
+ targets = get_target_args()
397
+ $deferr.puts '---', File.read( COMMIT_MSG_FILE ), '---'
398
+ ask_for_confirmation( "Continue with checkin?" ) do
399
+ run 'svn', 'ci', '-F', COMMIT_MSG_FILE, targets
400
+ rm_f COMMIT_MSG_FILE
401
+ end
402
+ end
403
+ task :commit => :checkin
404
+ task :ci => :checkin
405
+
406
+
407
+ task :clean do
408
+ rm_f COMMIT_MSG_FILE
409
+ end
410
+
411
+
412
+ desc "Check and fix any missing keywords for any files in the project which need them"
413
+ task :fix_keywords do
414
+ log "Checking subversion keywords..."
415
+ paths = get_svn_filelist( BASEDIR ).
416
+ select {|path| path.file? && path.to_s =~ KEYWORDED_FILEPATTERN }
417
+
418
+ trace "Looking at %d paths for keywords:\n %p" % [paths.length, paths]
419
+ kwmap = get_svn_keyword_map( paths )
420
+
421
+ buf = ''
422
+ PP.pp( kwmap, buf, 132 )
423
+ trace "keyword map is: %s" % [buf]
424
+
425
+ files_needing_fixups = paths.find_all do |path|
426
+ (kwmap[path.to_s] & DEFAULT_KEYWORDS) != DEFAULT_KEYWORDS
427
+ end
428
+
429
+ unless files_needing_fixups.empty?
430
+ $deferr.puts "Files needing keyword fixes: ",
431
+ files_needing_fixups.collect {|f|
432
+ " %s: %s" % [f, kwmap[f] ? kwmap[f].join(' ') : "(no keywords)"]
433
+ }
434
+ ask_for_confirmation( "Will add default keywords to these files." ) do
435
+ run 'svn', 'ps', 'svn:keywords', DEFAULT_KEYWORDS.join(' '), *files_needing_fixups
436
+ end
437
+ else
438
+ log "Keywords are all up to date."
439
+ end
440
+ end
441
+
442
+
443
+ task :debug_helpers do
444
+ methods = [
445
+ :make_new_tag,
446
+ :get_svn_info,
447
+ :get_svn_repo_root,
448
+ :get_svn_url,
449
+ :get_svn_path,
450
+ :svn_ls,
451
+ :get_latest_svn_timestamp_tag,
452
+ ]
453
+ maxlen = methods.collect {|sym| sym.to_s.length }.max
454
+
455
+ methods.each do |meth|
456
+ res = send( meth )
457
+ puts "%*s => %p" % [ maxlen, colorize(meth.to_s, :cyan), res ]
458
+ end
459
+ end
460
+ end
461
+
data/rake/testing.rb ADDED
@@ -0,0 +1,191 @@
1
+ #
2
+ # Rake tasklib for testing tasks
3
+ # $Id: testing.rb 14 2008-07-23 23:16:30Z deveiant $
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+
9
+ COVERAGE_MINIMUM = 85.0 unless defined?( COVERAGE_MINIMUM )
10
+ SPEC_FILES = [] unless defined?( SPEC_FILES )
11
+ TEST_FILES = [] unless defined?( TEST_FILES )
12
+
13
+ COMMON_SPEC_OPTS = ['-c', '-f', 's'] unless defined?( COMMON_SPEC_OPTS )
14
+
15
+ COVERAGE_TARGETDIR = BASEDIR + 'coverage' unless defined?( COVERAGE_TARGETDIR )
16
+ RCOV_EXCLUDES = 'spec,tests,/Library/Ruby,/var/lib,/usr/local/lib' unless
17
+ defined?( RCOV_EXCLUDES )
18
+
19
+
20
+ desc "Run all defined tests"
21
+ task :test do
22
+ unless SPEC_FILES.empty?
23
+ log "Running specs"
24
+ Rake::Task['spec:quiet'].invoke
25
+ end
26
+
27
+ unless TEST_FILES.empty?
28
+ log "Running unit tests"
29
+ Rake::Task[:unittests].invoke
30
+ end
31
+ end
32
+
33
+
34
+ ### RSpec specifications
35
+ begin
36
+ gem 'rspec', '>= 1.1.3'
37
+ require 'spec/rake/spectask'
38
+
39
+ ### Task: spec
40
+ Spec::Rake::SpecTask.new( :spec ) do |task|
41
+ task.spec_files = SPEC_FILES
42
+ task.libs += [LIBDIR]
43
+ task.spec_opts = COMMON_SPEC_OPTS
44
+ end
45
+
46
+
47
+ namespace :spec do
48
+ desc "Run rspec every time there's a change to one of the files"
49
+ task :autotest do
50
+ require 'autotest/rspec'
51
+
52
+ autotester = Autotest::Rspec.new
53
+ autotester.exceptions = %r{\.svn|\.skel}
54
+ autotester.run
55
+ end
56
+
57
+
58
+ desc "Generate quiet output"
59
+ Spec::Rake::SpecTask.new( :quiet ) do |task|
60
+ task.spec_files = SPEC_FILES
61
+ task.spec_opts = ['-f', 'p', '-D']
62
+ end
63
+
64
+ desc "Generate HTML output for a spec run"
65
+ Spec::Rake::SpecTask.new( :html ) do |task|
66
+ task.spec_files = SPEC_FILES
67
+ task.spec_opts = ['-f','h', '-D']
68
+ end
69
+
70
+ desc "Generate plain-text output for a CruiseControl.rb build"
71
+ Spec::Rake::SpecTask.new( :text ) do |task|
72
+ task.spec_files = SPEC_FILES
73
+ task.spec_opts = ['-f','p']
74
+ end
75
+ end
76
+ rescue LoadError => err
77
+ task :no_rspec do
78
+ $stderr.puts "Specification tasks not defined: %s" % [ err.message ]
79
+ end
80
+
81
+ task :spec => :no_rspec
82
+ namespace :spec do
83
+ task :autotest => :no_rspec
84
+ task :quiet => :no_rspec
85
+ task :html => :no_rspec
86
+ task :text => :no_rspec
87
+ end
88
+ end
89
+
90
+
91
+ ### Test::Unit tests
92
+ begin
93
+ require 'rake/testtask'
94
+
95
+ Rake::TestTask.new( :unittests ) do |task|
96
+ task.libs += [LIBDIR]
97
+ task.test_files = TEST_FILES
98
+ task.verbose = true
99
+ end
100
+
101
+ rescue LoadError => err
102
+ task :no_test do
103
+ $stderr.puts "Test tasks not defined: %s" % [ err.message ]
104
+ end
105
+
106
+ task :unittests => :no_rspec
107
+ end
108
+
109
+
110
+ ### RCov (via RSpec) tasks
111
+ begin
112
+ gem 'rcov'
113
+ gem 'rspec', '>= 1.1.3'
114
+
115
+ ### Task: coverage (via RCov)
116
+ ### Task: rcov
117
+ desc "Build test coverage reports"
118
+ unless SPEC_FILES.empty?
119
+ Spec::Rake::SpecTask.new( :coverage ) do |task|
120
+ task.spec_files = SPEC_FILES
121
+ task.libs += [LIBDIR]
122
+ task.spec_opts = ['-f', 'p', '-b']
123
+ task.rcov_opts = RCOV_OPTS
124
+ task.rcov = true
125
+ end
126
+ end
127
+ unless TEST_FILES.empty?
128
+ require 'rcov/rcovtask'
129
+
130
+ Rcov::RcovTask.new do |task|
131
+ task.libs += [LIBDIR]
132
+ task.test_files = TEST_FILES
133
+ task.verbose = true
134
+ task.rcov_opts = RCOV_OPTS
135
+ end
136
+ end
137
+
138
+
139
+ task :rcov => [:coverage] do; end
140
+
141
+ ### Other coverage tasks
142
+ namespace :coverage do
143
+ desc "Generate a detailed text coverage report"
144
+ Spec::Rake::SpecTask.new( :text ) do |task|
145
+ task.spec_files = SPEC_FILES
146
+ task.rcov_opts = RCOV_OPTS + ['--text-report']
147
+ task.rcov = true
148
+ end
149
+
150
+ desc "Show differences in coverage from last run"
151
+ Spec::Rake::SpecTask.new( :diff ) do |task|
152
+ task.spec_files = SPEC_FILES
153
+ task.rcov_opts = ['--text-coverage-diff']
154
+ task.rcov = true
155
+ end
156
+
157
+ ### Task: verify coverage
158
+ desc "Build coverage statistics"
159
+ VerifyTask.new( :verify => :rcov ) do |task|
160
+ task.threshold = COVERAGE_MINIMUM
161
+ end
162
+
163
+ desc "Run RCov in 'spec-only' mode to check coverage from specs"
164
+ Spec::Rake::SpecTask.new( :speconly ) do |task|
165
+ task.spec_files = SPEC_FILES
166
+ task.rcov_opts = ['--exclude', RCOV_EXCLUDES, '--text-report', '--save']
167
+ task.rcov = true
168
+ end
169
+ end
170
+
171
+ task :clobber_coverage do
172
+ rmtree( COVERAGE_TARGETDIR )
173
+ end
174
+
175
+ rescue LoadError => err
176
+ task :no_rcov do
177
+ $stderr.puts "Coverage tasks not defined: RSpec+RCov tasklib not available: %s" %
178
+ [ err.message ]
179
+ end
180
+
181
+ task :coverage => :no_rcov
182
+ task :clobber_coverage
183
+ task :rcov => :no_rcov
184
+ namespace :coverage do
185
+ task :text => :no_rcov
186
+ task :diff => :no_rcov
187
+ end
188
+ task :verify => :no_rcov
189
+ end
190
+
191
+
@@ -0,0 +1,64 @@
1
+ #####################################################################
2
+ ### S U B V E R S I O N T A S K S A N D H E L P E R S
3
+ #####################################################################
4
+
5
+ require 'rake/tasklib'
6
+
7
+ #
8
+ # Work around the inexplicable behaviour of the original RDoc::VerifyTask, which
9
+ # errors if your coverage isn't *exactly* the threshold.
10
+ #
11
+
12
+ # A task that can verify that the RCov coverage doesn't
13
+ # drop below a certain threshold. It should be run after
14
+ # running Spec::Rake::SpecTask.
15
+ class VerifyTask < Rake::TaskLib
16
+
17
+ COVERAGE_PERCENTAGE_PATTERN =
18
+ %r{<tt class='coverage_code'>(\d+\.\d+)%</tt>}
19
+
20
+ # Name of the task. Defaults to :verify_rcov
21
+ attr_accessor :name
22
+
23
+ # Path to the index.html file generated by RCov, which
24
+ # is the file containing the total coverage.
25
+ # Defaults to 'coverage/index.html'
26
+ attr_accessor :index_html
27
+
28
+ # Whether or not to output details. Defaults to true.
29
+ attr_accessor :verbose
30
+
31
+ # The threshold value (in percent) for coverage. If the
32
+ # actual coverage is not equal to this value, the task will raise an
33
+ # exception.
34
+ attr_accessor :threshold
35
+
36
+ def initialize( name=:verify )
37
+ @name = name
38
+ @index_html = 'coverage/index.html'
39
+ @verbose = true
40
+ yield self if block_given?
41
+ raise "Threshold must be set" if @threshold.nil?
42
+ define
43
+ end
44
+
45
+ def define
46
+ desc "Verify that rcov coverage is at least #{threshold}%"
47
+
48
+ task @name do
49
+ total_coverage = nil
50
+ if match = File.read( index_html ).match( COVERAGE_PERCENTAGE_PATTERN )
51
+ total_coverage = Float( match[1] )
52
+ else
53
+ raise "Couldn't find the coverage percentage in #{index_html}"
54
+ end
55
+
56
+ puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose
57
+ if total_coverage < threshold
58
+ raise "Coverage must be at least #{threshold}% but was #{total_coverage}%"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # vim: set nosta noet ts=4 sw=4: