pluginfactory 1.0.2 → 1.0.3

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