dev-utils 1.0
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/HISTORY.txt +36 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.txt +21 -0
- data/Rakefile +135 -0
- data/VERSION +1 -0
- data/etc/doc/DebuggingAids.textile +450 -0
- data/etc/doc/UnitTestOrganisation.textile +253 -0
- data/etc/doc/generate.rb +187 -0
- data/etc/doc/index.textile +110 -0
- data/etc/doc/links.dat +9 -0
- data/etc/doc/textile.css +150 -0
- data/examples/breakpoint-example.rb +30 -0
- data/examples/debug.log +0 -0
- data/examples/log-trace-example.rb +35 -0
- data/lib/dev-utils/debug.rb +32 -0
- data/lib/dev-utils/debug/diff.rb +187 -0
- data/lib/dev-utils/debug/irb.rb +176 -0
- data/lib/dev-utils/debug/log.rb +107 -0
- data/lib/dev-utils/test.rb +267 -0
- data/test/TEST.rb +6 -0
- data/test/tc_debug.rb +153 -0
- metadata +76 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
# = dev-utils/debug/irb.rb
|
2
|
+
#
|
3
|
+
# DevUtils::Debug.breakpoint and some aliases are implemented here. Do not load this file
|
4
|
+
# directly.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'irb'
|
8
|
+
require 'extensions/binding'
|
9
|
+
|
10
|
+
module DevUtils::Debug
|
11
|
+
|
12
|
+
#
|
13
|
+
# Method:: *breakpoint*
|
14
|
+
# Aliases:: *break_point*, *goirb*
|
15
|
+
#
|
16
|
+
# === Description
|
17
|
+
#
|
18
|
+
# This will pop up an interactive ruby session from whereever it is called in a Ruby
|
19
|
+
# application. In IRB you can examine the environment of the break point, peeking
|
20
|
+
# and poking local variables, calling methods, viewing the stack (with +caller+), etc.
|
21
|
+
#
|
22
|
+
# This is like setting breakpoints in a debugger, except that you are running the program
|
23
|
+
# normally (debuggers tend to run programs more slowly). Debuggers are generally more
|
24
|
+
# flexible, but this is a good lightweight solution for many cases. You <em>can not</em>
|
25
|
+
# step through the code with this technique. But you can, of course, set multiple
|
26
|
+
# breakpoints. And you can make breakpoints conditional.
|
27
|
+
#
|
28
|
+
# You can force a breakpoint to return a certain value. This is typically only useful if
|
29
|
+
# the breakpoint is the last value in a method, as this will cause the method to return a
|
30
|
+
# different value than normal. This is demonstrated in the example below.
|
31
|
+
#
|
32
|
+
# You can also give names to break points which will be used in the message that is
|
33
|
+
# displayed upon execution of them. This helps to differentiate them at runtime if you
|
34
|
+
# have set several breakpoints in the code.
|
35
|
+
#
|
36
|
+
# === Parameters
|
37
|
+
#
|
38
|
+
# _name_:: A String to identify the breakpoint, giving a more informative message.
|
39
|
+
# _context_:: Any object; IRB will use this as its context. The default is the current
|
40
|
+
# scope's binding, which is nearly always what you will want.
|
41
|
+
# _block_:: Will be executed when the breakpoint returns normally. Bypassed if you
|
42
|
+
# <tt>throw :debug_return, </tt><em>value</em> from IRB. Unless you are
|
43
|
+
# _planning_ to use the <tt>debug_return</tt> feature for a given breakpoint,
|
44
|
+
# you don't need to worry about the block.
|
45
|
+
#
|
46
|
+
# === Typical Invocation
|
47
|
+
#
|
48
|
+
# breakpoint # Plain message.
|
49
|
+
# breakpoint "Person#name" # More informative message.
|
50
|
+
# breakpoint { normal_return_value }
|
51
|
+
#
|
52
|
+
# === Example
|
53
|
+
#
|
54
|
+
# Here's a sample of how breakpoints should be placed:
|
55
|
+
#
|
56
|
+
# require 'dev-utils/debug'
|
57
|
+
#
|
58
|
+
# class Person
|
59
|
+
# def initialize(name, age)
|
60
|
+
# @name, @age = name, age
|
61
|
+
# breakpoint "Person#initialize"
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# attr_reader :age
|
65
|
+
# def name
|
66
|
+
# breakpoint "Person#name" { @name }
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# person = Person.new("Random Person", 23)
|
71
|
+
# puts "Name: #{person.name}"
|
72
|
+
#
|
73
|
+
# And here is a sample debug session:
|
74
|
+
#
|
75
|
+
# Executing break point "Person#initialize" at file.rb:4 in `initialize'
|
76
|
+
# irb(#<Person:0x292fbe8>):001:0> <b>local_variables</b>
|
77
|
+
# => ["name", "age", "_", "__"]
|
78
|
+
# irb(#<Person:0x292fbe8>):002:0> [name, age]
|
79
|
+
# => ["Random Person", 23]
|
80
|
+
# irb(#<Person:0x292fbe8>):003:0> [@name, @age]
|
81
|
+
# => ["Random Person", 23]
|
82
|
+
# irb(#<Person:0x292fbe8>):004:0> self
|
83
|
+
# => #<Person:0x292fbe8 @age=23, @name="Random Person">
|
84
|
+
# irb(#<Person:0x292fbe8>):005:0> @age += 1; self
|
85
|
+
# => #<Person:0x292fbe8 @age=24, @name="Random Person">
|
86
|
+
# irb(#<Person:0x292fbe8>):006:0> exit
|
87
|
+
# Executing break point "Person#name" at file.rb:9 in `name'
|
88
|
+
# irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
|
89
|
+
# Name: Overriden name
|
90
|
+
#
|
91
|
+
# This example is explored more thoroughly at
|
92
|
+
# http://dev-utils.rubyforge.org/DebuggingAids.html.
|
93
|
+
#
|
94
|
+
# === Credits
|
95
|
+
#
|
96
|
+
# Joel VanderWerf and Florian Gross have contributed the code and documentation for this
|
97
|
+
# method.
|
98
|
+
#
|
99
|
+
def breakpoint(name = nil, context = nil, &block)
|
100
|
+
file, line, method = *caller.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
|
101
|
+
message = "Executing breakpoint #{name.inspect if name} at #{file}:#{line}"
|
102
|
+
message << " in '#{method}'" if method
|
103
|
+
|
104
|
+
body = lambda do |_context|
|
105
|
+
puts message
|
106
|
+
catch(:debug_return) do |value|
|
107
|
+
IRB.start_session(IRB::WorkSpace.new(_context))
|
108
|
+
block.call if block
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Run IRB with the given context, if it _is_ given.
|
113
|
+
return body.call(context) if context
|
114
|
+
# Otherwise, run IRB with the parent scope's binding, giving access to
|
115
|
+
# the local variables of the method that called method.
|
116
|
+
Binding.of_caller do |binding_context|
|
117
|
+
body.call(binding_context)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
alias :break_point :breakpoint
|
122
|
+
alias :goirb :breakpoint
|
123
|
+
|
124
|
+
alias lv local_variables
|
125
|
+
alias iv instance_variables
|
126
|
+
end
|
127
|
+
|
128
|
+
# :stopdoc:
|
129
|
+
|
130
|
+
module ::IRB
|
131
|
+
|
132
|
+
class << self; remove_method :parse_opts; end
|
133
|
+
def IRB.parse_opts
|
134
|
+
# Don't touch ARGV, which belongs to the app which called this module.
|
135
|
+
end
|
136
|
+
|
137
|
+
def IRB.start_session(object)
|
138
|
+
unless $irb
|
139
|
+
IRB.setup nil
|
140
|
+
## maybe set some opts here, as in parse_opts in irb/init.rb?
|
141
|
+
end
|
142
|
+
|
143
|
+
@CONF[:PROMPT_MODE] = :SIMPLE
|
144
|
+
|
145
|
+
workspace = WorkSpace.new(object)
|
146
|
+
|
147
|
+
if @CONF[:SCRIPT] ## normally, set by parse_opts
|
148
|
+
$irb = Irb.new(workspace, @CONF[:SCRIPT])
|
149
|
+
else
|
150
|
+
$irb = Irb.new(workspace)
|
151
|
+
end
|
152
|
+
|
153
|
+
@CONF[:IRB_RC].call($irb.context) if @CONF[:IRB_RC]
|
154
|
+
@CONF[:MAIN_CONTEXT] = $irb.context
|
155
|
+
|
156
|
+
trap 'INT' do
|
157
|
+
$irb.signal_handle
|
158
|
+
end
|
159
|
+
|
160
|
+
custom_configuration if defined?(IRB.custom_configuration)
|
161
|
+
|
162
|
+
catch :IRB_EXIT do
|
163
|
+
$irb.eval_input
|
164
|
+
end
|
165
|
+
|
166
|
+
puts
|
167
|
+
|
168
|
+
## might want to reset your app's interrupt handler here
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class Object
|
173
|
+
include IRB::ExtendCommandBundle # so that Marshal.dump works
|
174
|
+
end
|
175
|
+
|
176
|
+
# :startdoc:
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# = dev-utils/debug/log.rb
|
2
|
+
#
|
3
|
+
# DevUtils::Debug.{debug,trace,logger=,logfile=} are implemented here. Do not load this file
|
4
|
+
# directly.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'logger'
|
8
|
+
require 'extensions/binding'
|
9
|
+
require 'extensions/object'
|
10
|
+
|
11
|
+
module DevUtils::Debug
|
12
|
+
|
13
|
+
# The target of logging messages from _debug_ and _trace_.
|
14
|
+
DEBUGLOG = Logger.new(File.new('debug.log', 'w'))
|
15
|
+
DEBUGLOG.datetime_format = " \010"
|
16
|
+
DEBUGLOG.progname = "\010\010\010"
|
17
|
+
|
18
|
+
#
|
19
|
+
# Write _message_ to the debugging log.
|
20
|
+
#
|
21
|
+
# The <em>debugging log</em> is a zero-conf logfile. Here is an example usage:
|
22
|
+
#
|
23
|
+
# $ cat example.rb
|
24
|
+
#
|
25
|
+
# require 'dev-utils/debug'
|
26
|
+
#
|
27
|
+
# debug "Setting variables x and y"
|
28
|
+
# x = 5; y = 17
|
29
|
+
# trace 'x + y'
|
30
|
+
# puts "Finished"
|
31
|
+
#
|
32
|
+
# $ ruby example.rb
|
33
|
+
# Finished
|
34
|
+
#
|
35
|
+
# $ cat debug.log
|
36
|
+
# D, [#244] DEBUG : Setting variables x and y
|
37
|
+
# D, [#244] DEBUG : x + y = 22
|
38
|
+
#
|
39
|
+
# Simply with <tt>require 'dev-utils/debug'</tt>, you have availed yourself of a handy
|
40
|
+
# debugging log file which you don't have to create.
|
41
|
+
#
|
42
|
+
# See also the _trace_ method.
|
43
|
+
#
|
44
|
+
def debug(message)
|
45
|
+
DEBUGLOG.debug message
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Prints a trace message to DEBUGLOG (at debug level). Useful for emitting
|
50
|
+
# the value of variables, etc. Use like this:
|
51
|
+
#
|
52
|
+
# x = y = 5
|
53
|
+
# trace 'x' # -> 'x = 5'
|
54
|
+
# trace 'x ** y' # -> 'x ** y = 3125'
|
55
|
+
#
|
56
|
+
# If you have a more complicated value, like an array of hashes, then you'll probably want
|
57
|
+
# to use an alternative output format. For instance:
|
58
|
+
#
|
59
|
+
# trace 'value', :yaml
|
60
|
+
#
|
61
|
+
# Valid output format values (the _style_ parameter) are:
|
62
|
+
#
|
63
|
+
# :p :inspect
|
64
|
+
# :pp (pretty-print, using 'pp' library)
|
65
|
+
# :s :to_s
|
66
|
+
# :y :yaml :to_yaml (using the 'yaml' library')
|
67
|
+
#
|
68
|
+
# The default is <tt>:p</tt>.
|
69
|
+
#
|
70
|
+
def trace(expr, style=:p)
|
71
|
+
unless expr.respond_to? :to_str
|
72
|
+
message = "trace: Can't evaluate the given value: #{caller.first}"
|
73
|
+
DEBUGLOG.warn message
|
74
|
+
else
|
75
|
+
Binding.of_caller do |b|
|
76
|
+
value = b.eval(expr.to_str)
|
77
|
+
formatter = TRACE_STYLES[style] || :inspect
|
78
|
+
case formatter
|
79
|
+
when :pp then require 'pp'
|
80
|
+
when :y, :yaml, :to_yaml then require 'yaml'
|
81
|
+
end
|
82
|
+
value_s = value.send(formatter)
|
83
|
+
message = "#{expr} = #{value_s}"
|
84
|
+
lines = message.split(/\n/)
|
85
|
+
indent = " "
|
86
|
+
DEBUGLOG.debug lines.shift # First line unindented.
|
87
|
+
lines.each do |line|
|
88
|
+
DEBUGLOG.debug(indent + line) # Other lines indented.
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
TRACE_STYLES = {} # :nodoc:
|
94
|
+
TRACE_STYLES.update( :pp => :pp_s, :s => :to_s, :p => :inspect, :y => :to_yaml,
|
95
|
+
:yaml => :to_yaml, :inspect => :inspect, :to_yaml => :to_yaml )
|
96
|
+
|
97
|
+
# Planned feature; not yet implemented.
|
98
|
+
def logger=(x)
|
99
|
+
warn 'logger= is not implemented; ignoring'
|
100
|
+
end
|
101
|
+
|
102
|
+
# Planned feature; not yet implemented.
|
103
|
+
def logfile=(x)
|
104
|
+
warn 'logfile= is not implemented; ignoring'
|
105
|
+
end
|
106
|
+
|
107
|
+
end # module DevUtils::Debug
|
@@ -0,0 +1,267 @@
|
|
1
|
+
#
|
2
|
+
# = dev-utils/test.rb
|
3
|
+
#
|
4
|
+
# _Planned_ functionality to support <b>unit testing</b>. Nothing here at the moment.
|
5
|
+
#
|
6
|
+
|
7
|
+
=begin
|
8
|
+
|
9
|
+
#
|
10
|
+
# = dev-utils/test.rb
|
11
|
+
#
|
12
|
+
# Testing utilities DevUtils::Test.{load_tests,load_data} and various project directory access
|
13
|
+
# methods are implemented here. See DevUtils::Test for documentation.
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'test/unit'
|
17
|
+
require 'test/unit/ui/console/testrunner'
|
18
|
+
require 'optparse'
|
19
|
+
require 'ostruct'
|
20
|
+
|
21
|
+
module DevUtils
|
22
|
+
#
|
23
|
+
# ...
|
24
|
+
#
|
25
|
+
class Test
|
26
|
+
|
27
|
+
#
|
28
|
+
# Loads all unit test files matching the arguments given in the sense described below.
|
29
|
+
# A typical use for this is:
|
30
|
+
#
|
31
|
+
# DevUtils::Test.load_tests(__FILE__, *ARGV)
|
32
|
+
#
|
33
|
+
# That will load all 'tc_*.rb' files in or below the directory in which the calling
|
34
|
+
# program is located. If any arguments are provides, they are substrings used to
|
35
|
+
# restrict the tests that are run, giving the user an easy way to select a unit test.
|
36
|
+
#
|
37
|
+
# For example, if the arguments "str" and "num" are given, then for any test file to be
|
38
|
+
# loaded, its filename must contain either or both of those substrings. It's a hassle
|
39
|
+
# for the user to type the whole filename, so this makes test selection more practical.
|
40
|
+
#
|
41
|
+
# I'll experiment with accepting other arguments like "-q" and "-v" to mean "quiet" and
|
42
|
+
# "verbose" respectively. Verbose is default, meaning each test filename is relayed to
|
43
|
+
# STDERR to inform the user what's going on. Other arguments: "-h" for help (to explain
|
44
|
+
# this whole thing), and "-s" for separate running of each test, instead of running all
|
45
|
+
# tests in one big suite.
|
46
|
+
#
|
47
|
+
# The <b>ultimate purpose</b> of this is to implement a unit test suite runner in a
|
48
|
+
# reusable fashion, rather than coding one up for each project. Therefore, the file
|
49
|
+
# <tt>test/TEST.rb</tt>, for instance, would consist of:
|
50
|
+
#
|
51
|
+
# require 'rubygems'
|
52
|
+
# require 'dev-utils/test'
|
53
|
+
#
|
54
|
+
# DevUtils::Test.load_tests(__FILE__, ARGV)
|
55
|
+
#
|
56
|
+
# The user can then simply run:
|
57
|
+
#
|
58
|
+
# ruby test/TEST.rb -h # get help on options
|
59
|
+
# ruby test/TEST.rb -q # quiet operation
|
60
|
+
# ruby test/TEST.rb -s # separate suite per test file
|
61
|
+
# ruby test/TEST.rb str io # only tests that have "str" or "io" in the filename
|
62
|
+
#
|
63
|
+
# The convention of naming test files "tc_XYZ.rb" must be followed for this to work.
|
64
|
+
#
|
65
|
+
def self.load_tests(path, args)
|
66
|
+
if self.project_dir.nil?
|
67
|
+
# This is the usual case. We derive the project directory from the path. The fact
|
68
|
+
# that @project_dir is nil means that all defaults have been accepted. Therefore we
|
69
|
+
# must be in the +test+ directory.
|
70
|
+
path = File.expand_path(path)
|
71
|
+
unless FileTest.exist?(path)
|
72
|
+
raise RuntimeError, "Internal state problem: path '#{path}' doesn't exist"
|
73
|
+
end
|
74
|
+
test_dir = File.dirname(path)
|
75
|
+
project_dir = File.dirname(test_dir)
|
76
|
+
self.project_dir = project_dir
|
77
|
+
_assert_path self.lib_dir
|
78
|
+
_assert_path self.test_dir
|
79
|
+
# We don't assert the existence of the test data directory because it's not
|
80
|
+
# mandatory.
|
81
|
+
else
|
82
|
+
# In this case, the project directory has been specified prior to calling this
|
83
|
+
# method. We will simply use the values provided or derived.
|
84
|
+
end
|
85
|
+
|
86
|
+
# The main things to do in this method: modify the load path; parse the arguments
|
87
|
+
# given, decide which test files to load, and load them.
|
88
|
+
|
89
|
+
# 1. Handle the load path.
|
90
|
+
$:.unshift self.lib_dir
|
91
|
+
|
92
|
+
# 2. Parse the arguments given to this method.
|
93
|
+
opts = _parse_options(args)
|
94
|
+
|
95
|
+
Dir.chdir(self.test_dir) do
|
96
|
+
# 3. Decide which test files to load.
|
97
|
+
test_files = Dir['**/tc_*.rb']
|
98
|
+
unless opts.patterns.empty?
|
99
|
+
test_files = test_files.select { |fn| opts.patterns.any? { |str| fn.index(str) } }
|
100
|
+
end
|
101
|
+
|
102
|
+
# 4. Load them, using separate test suites if requested.
|
103
|
+
test_files.each do |test_file|
|
104
|
+
STDERR.puts "Loading test file: #{test_file}" if opts.mode == :verbose
|
105
|
+
require test_file
|
106
|
+
# TODO: take care of separate test suites, and respect --very-quiet.
|
107
|
+
end
|
108
|
+
if opts.separate
|
109
|
+
test_cases = []
|
110
|
+
ObjectSpace.each_object(Class) do |klass|
|
111
|
+
test_cases << klass if klass < ::Test::Unit::TestCase
|
112
|
+
end
|
113
|
+
test_cases.sort_by { |c| c.name }.each do |klass|
|
114
|
+
STDERR.puts "Running test class #{klass}" if opts.mode == :verbose
|
115
|
+
output_level =
|
116
|
+
case opts.mode
|
117
|
+
when :veryquiet then ::Test::Unit::UI::SILENT
|
118
|
+
when :quiet then ::Test::Unit::UI::PROGRESS_ONLY
|
119
|
+
when :verbose then ::Test::Unit::UI::NORMAL
|
120
|
+
end
|
121
|
+
runner = ::Test::Unit::UI::Console::TestRunner.new(klass.suite, output_level)
|
122
|
+
runner.start
|
123
|
+
end
|
124
|
+
else
|
125
|
+
# We let all test cases run in a single suite on exit.
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end # def load_tests
|
129
|
+
|
130
|
+
#
|
131
|
+
# This method is aimed at allowing unit tests to access data resources easily. Those
|
132
|
+
# resources are files buried in a <i>test data</i> directory, so they do not clutter the
|
133
|
+
# unit tests themselves.
|
134
|
+
#
|
135
|
+
# You access a test data resource by its path relative to the test data directory. The
|
136
|
+
# unit test runs blissfully unaware of where that directory is. See ... for a
|
137
|
+
# demonstration. (TODO: write a high-level document that ties this all together.)
|
138
|
+
#
|
139
|
+
# Once the test data resource is located, the default is to load the file and return its
|
140
|
+
# contents as a string. You can get different behaviour by specifying the XXX
|
141
|
+
#
|
142
|
+
# The default is to return a String. The +flags+ provided affect this:
|
143
|
+
# * <tt>:string</tt> means return a String
|
144
|
+
# * <tt>:file</tt> means return a File
|
145
|
+
# * <tt>:pathname</tt> means return a Pathname
|
146
|
+
# * <tt>:copy</tt> means return a _copy_ of the file (<tt>:file</tt> and
|
147
|
+
# <tt>:pathname</tt> only)
|
148
|
+
# * <tt>:write</tt> means resource is intended to be _written_, therefore don't complain
|
149
|
+
# if it doesn't exist (<tt>:file</tt> and <tt>:pathname</tt> only)
|
150
|
+
#
|
151
|
+
# An error of some sort is raised if the file doesn't exist, unless <tt>:write</tt> is
|
152
|
+
# specified.
|
153
|
+
#
|
154
|
+
def self.load_data(resource_path, *flags)
|
155
|
+
if self.data_directory.nil?
|
156
|
+
raise RuntimeError,
|
157
|
+
"Test data directory hasn't been defined. Use #{self}.data_directory="
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.set_project_dir_from(path, project_name)
|
162
|
+
path = File.expand_path(path)
|
163
|
+
_assert_path path
|
164
|
+
# The project dir is everything in the path up to and including the first instance of the
|
165
|
+
# project name.
|
166
|
+
if path =~ /\A(.*?#{project_name}).*\Z/
|
167
|
+
self.project_dir = $1
|
168
|
+
else
|
169
|
+
raise RuntimeError, "Project name '#{project_name}' not found in path '#{path}'"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.project_dir=(path)
|
174
|
+
path = File.expand_path(path)
|
175
|
+
_assert_path path
|
176
|
+
@project_dir = path
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.project_dir
|
180
|
+
@project_dir
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.lib_dir=(path)
|
184
|
+
raise RuntimeError, "Project directory not set" unless project_dir
|
185
|
+
_assert_path path
|
186
|
+
@lib_dir = File.join(project_dir, path)
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.lib_dir
|
190
|
+
return @lib_dir if @lib_dir
|
191
|
+
File.join(project_dir, 'lib')
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.test_dir=(path)
|
195
|
+
raise RuntimeError, "Project directory not set" unless project_dir
|
196
|
+
_assert_path path
|
197
|
+
@test_dir = File.join(project_dir, path)
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.test_dir
|
201
|
+
return @test_dir if @test_dir
|
202
|
+
File.join(project_dir, 'test')
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.data_dir=(path)
|
206
|
+
raise RuntimeError, "Project directory not set" unless project_dir
|
207
|
+
_assert_path path
|
208
|
+
@data_dir = File.join(project_dir, path)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.data_dir
|
212
|
+
return @data_dir if @data_dir
|
213
|
+
File.join(project_dir, 'data')
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
def self._assert_path(path)
|
219
|
+
unless FileTest.exist?(path)
|
220
|
+
raise RuntimeError, "Path doesn't exist: #{path}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Parse the given arguments for the test loader. Return an OpenStruct with 'mode',
|
226
|
+
# 'separate', and 'patterns' attributes.
|
227
|
+
#
|
228
|
+
def self._parse_options(args)
|
229
|
+
opts = OpenStruct.new
|
230
|
+
opts.mode = :verbose
|
231
|
+
opts.separate = false
|
232
|
+
opts.patterns = []
|
233
|
+
parser = OptionParser.new do |op|
|
234
|
+
op.banner =<<EOF
|
235
|
+
|
236
|
+
Welcome to the dev-utils/test automatic unit test runner.
|
237
|
+
Here are the options:
|
238
|
+
|
239
|
+
EOF
|
240
|
+
op.on('-q', '--quiet', "Quiet mode") { opts.mode = :quiet }
|
241
|
+
op.on('-Q', '--very-quiet', "Very quiet mode") { opts.mode = :veryquiet }
|
242
|
+
op.on('-v', '--verbose', "Verbose mode (default)") { opts.mode = :verbose }
|
243
|
+
op.on('-s', '--separate', "Separate test suite for each test file") { opts.separate = true }
|
244
|
+
op.on('-h', '--help', "Show this message") { _show_help(op) }
|
245
|
+
op.separator ""
|
246
|
+
op.separator " For example, to run only those tests whose filenames include 'remote' or"
|
247
|
+
op.separator " 'db', and to run a separate suite for each test file, and to run in quite"
|
248
|
+
op.separator " mode:"
|
249
|
+
op.separator ""
|
250
|
+
op.separator " ruby test/TEST.rb -q remote db -s"
|
251
|
+
end
|
252
|
+
parser.parse!(args)
|
253
|
+
opts.patterns = args.dup
|
254
|
+
args.clear
|
255
|
+
opts
|
256
|
+
end
|
257
|
+
|
258
|
+
def self._show_help(parser)
|
259
|
+
STDERR.puts parser
|
260
|
+
exit
|
261
|
+
end
|
262
|
+
|
263
|
+
end # class Test
|
264
|
+
|
265
|
+
end # module DevUtils
|
266
|
+
|
267
|
+
=end
|