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