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
data/etc/doc/links.dat
ADDED
data/etc/doc/textile.css
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
body {
|
3
|
+
font-family: georgia, verdana, serif;
|
4
|
+
margin: 20px 50px;
|
5
|
+
width: 45em;
|
6
|
+
}
|
7
|
+
|
8
|
+
.small-title {
|
9
|
+
font-family: courier new;
|
10
|
+
font-size: 10pt;
|
11
|
+
margin: 0px;
|
12
|
+
color: green;
|
13
|
+
}
|
14
|
+
|
15
|
+
.mylink {
|
16
|
+
font-family: georgia, verdana, serif;
|
17
|
+
font-size: 10pt;
|
18
|
+
}
|
19
|
+
|
20
|
+
h1 {
|
21
|
+
border-bottom: 1px solid;
|
22
|
+
padding-bottom: 3px;
|
23
|
+
margin-top: 2px;
|
24
|
+
color: green;
|
25
|
+
font-size: 30pt;
|
26
|
+
}
|
27
|
+
|
28
|
+
h2 {
|
29
|
+
font-size: 20pt;
|
30
|
+
color: darkblue;
|
31
|
+
}
|
32
|
+
|
33
|
+
h3 {
|
34
|
+
font-size: 16pt;
|
35
|
+
color: darkblue;
|
36
|
+
}
|
37
|
+
|
38
|
+
p,li {
|
39
|
+
margin: 10px 15px 5px 15px;
|
40
|
+
font-size: 12pt;
|
41
|
+
line-height: 1.5em;
|
42
|
+
text-align: justify;
|
43
|
+
}
|
44
|
+
|
45
|
+
p {
|
46
|
+
margin-top: 18px;
|
47
|
+
}
|
48
|
+
|
49
|
+
code {
|
50
|
+
padding-left: 0.15em;
|
51
|
+
padding-right: 0.1em;
|
52
|
+
background-color: #f9f9f9;
|
53
|
+
}
|
54
|
+
|
55
|
+
pre {
|
56
|
+
margin-left: 3em;
|
57
|
+
background-color: #f6f6f6;
|
58
|
+
padding-top: 1ex;
|
59
|
+
padding-bottom: 1ex;
|
60
|
+
}
|
61
|
+
|
62
|
+
.comment {
|
63
|
+
margin-left: 4em;
|
64
|
+
margin-right: 2em;
|
65
|
+
font-size: 11pt;
|
66
|
+
font-style: italic;
|
67
|
+
color: darkgreen;
|
68
|
+
}
|
69
|
+
|
70
|
+
/* Planned functionality. */
|
71
|
+
|
72
|
+
.planned {
|
73
|
+
border: 1px dotted lightgray;
|
74
|
+
background-color: rgb(224,255,221);
|
75
|
+
padding: 5px;
|
76
|
+
}
|
77
|
+
|
78
|
+
div.planned {
|
79
|
+
margin-top: 20pt;
|
80
|
+
}
|
81
|
+
|
82
|
+
.planned code, .planned pre {
|
83
|
+
background-color: rgb(219,255,215);
|
84
|
+
}
|
85
|
+
|
86
|
+
/* Table of contents and links. */
|
87
|
+
|
88
|
+
.toc_table {
|
89
|
+
font: 10pt, verdana,sans-serif;
|
90
|
+
}
|
91
|
+
|
92
|
+
.toc_table .header {
|
93
|
+
font-weight: bold;
|
94
|
+
color: #666;
|
95
|
+
}
|
96
|
+
|
97
|
+
.toc_table a {
|
98
|
+
text-decoration: none;
|
99
|
+
}
|
100
|
+
|
101
|
+
.toc_table td {
|
102
|
+
vertical-align: top;
|
103
|
+
padding-left: 1em;
|
104
|
+
padding-right: 1em;
|
105
|
+
}
|
106
|
+
|
107
|
+
.toc_table ul {
|
108
|
+
margin-left: 1em;
|
109
|
+
padding-left: 1em;
|
110
|
+
}
|
111
|
+
|
112
|
+
.toc_table ul li ul {
|
113
|
+
margin-left: 1ex;
|
114
|
+
padding-left: 1ex;
|
115
|
+
}
|
116
|
+
|
117
|
+
.toc_table ul li {
|
118
|
+
padding-left: 0;
|
119
|
+
margin-left: 0;
|
120
|
+
font-family: Arial, sans-serif;
|
121
|
+
font-size: 10pt;
|
122
|
+
text-align: left;
|
123
|
+
line-height: 9pt;
|
124
|
+
list-style-type: square;
|
125
|
+
color: #CCC;
|
126
|
+
}
|
127
|
+
|
128
|
+
.toc_table ul li ul li {
|
129
|
+
list-style-type: none;
|
130
|
+
font-style: italic;
|
131
|
+
}
|
132
|
+
|
133
|
+
#contents li a {
|
134
|
+
color: #55A;
|
135
|
+
}
|
136
|
+
|
137
|
+
#contents li a:hover {
|
138
|
+
color: #66F;
|
139
|
+
}
|
140
|
+
|
141
|
+
#links li a {
|
142
|
+
color: #5A5;
|
143
|
+
}
|
144
|
+
|
145
|
+
#links li a:hover {
|
146
|
+
color: #6D6;
|
147
|
+
}
|
148
|
+
|
149
|
+
/* vim: sw=2 ts=2 et sts=2
|
150
|
+
*/
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# To run this example, simply do
|
3
|
+
#
|
4
|
+
# ruby breakpoint-exqample.rb
|
5
|
+
#
|
6
|
+
# It will escape to IRB at various points. You can query the local variables, etc., and even
|
7
|
+
# try a "throw :debug_return, 5" to force a return from the method.
|
8
|
+
#
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'rubygems'
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
require 'dev-utils/debug'
|
15
|
+
|
16
|
+
class Person
|
17
|
+
def initialize(name, age)
|
18
|
+
@name, @age = name, age
|
19
|
+
breakpoint 'Person#initialize'
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :age
|
23
|
+
def name
|
24
|
+
breakpoint('Person#name') { @name }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
person = Person.new('John Smith', 23)
|
29
|
+
puts "Name: #{person.name}"
|
30
|
+
|
data/examples/debug.log
ADDED
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# To run this example, just do
|
3
|
+
#
|
4
|
+
# ruby log-trace-example.rb
|
5
|
+
#
|
6
|
+
# You should see a file "debug.log" in your current directory.
|
7
|
+
#
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'rubygems'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
require 'dev-utils/debug'
|
14
|
+
require 'extensions/enumerable' # dev-utils depends on extensions anyway.
|
15
|
+
|
16
|
+
debug "Running sanity check of dev-utils/debug logging and tracing."
|
17
|
+
|
18
|
+
x, y = 5, 10
|
19
|
+
trace 'x + y'
|
20
|
+
trace 'Process.pid'
|
21
|
+
|
22
|
+
debug "Now we test the various output formatters."
|
23
|
+
|
24
|
+
words = %w(wren fibonnaci smelt bovine smeglicious craptacular
|
25
|
+
inoccidental myrmidon boondoggle)
|
26
|
+
word_lengths = words.build_hash { |word| [word, word.length] }
|
27
|
+
|
28
|
+
[:p, :s, :pp, :y].each_with_index do |symbol, idx|
|
29
|
+
debug ''
|
30
|
+
debug "#{idx+1}. #{symbol.inspect} format"
|
31
|
+
trace 'words', symbol
|
32
|
+
debug ''
|
33
|
+
trace 'word_lengths', symbol
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# = dev-utils/debug.rb
|
3
|
+
#
|
4
|
+
# See DevUtils::Debug for documentation.
|
5
|
+
#
|
6
|
+
|
7
|
+
#
|
8
|
+
# Base module for <tt>dev-utils</tt>.
|
9
|
+
#
|
10
|
+
module DevUtils
|
11
|
+
#
|
12
|
+
# <tt>DevUtils::Debug</tt> contains methods to aid debugging Ruby programs, although when
|
13
|
+
# using these methods, you don't care about the module; it is included into the top-level
|
14
|
+
# when you <code>require 'dev-utils/debug'</code>.
|
15
|
+
#
|
16
|
+
# The methods are:
|
17
|
+
# * #breakpoint, for escaping to IRB from a running program, with local environment intact;
|
18
|
+
# * #debug, for logging debugging messages to a zero-conf logfile; and
|
19
|
+
# * #trace, for tracing expressions to that same file.
|
20
|
+
#
|
21
|
+
# Planned features include a method for determining the difference between two complex
|
22
|
+
# objects.
|
23
|
+
#
|
24
|
+
module Debug
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'dev-utils/debug/irb'
|
29
|
+
require 'dev-utils/debug/log'
|
30
|
+
#require 'dev-utils/debug/diff'
|
31
|
+
|
32
|
+
include DevUtils::Debug
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# = dev-utils/debug/diff.rb
|
2
|
+
#
|
3
|
+
# _Planned_ functionality to support <b>comparing complex objects</b> and <b>discovering object
|
4
|
+
# topologies</b>. Nothing here for now.
|
5
|
+
#
|
6
|
+
|
7
|
+
=begin
|
8
|
+
|
9
|
+
# Implement Debug.diff here. Do not load this file directly.
|
10
|
+
|
11
|
+
module DevUtils::Debug
|
12
|
+
#
|
13
|
+
# Helps identify the difference between two objects when it's not immediately obvious to
|
14
|
+
# the eye.
|
15
|
+
#
|
16
|
+
# +o1+ and +o2+ are simply objects to be compared. If one or the other is a built-in type,
|
17
|
+
# like String, then it's a simple yes/no answer and this method doesn't buy you much. If
|
18
|
+
# they are both complex objects (aggregating other data, like a Struct or data object),
|
19
|
+
# then the comparison takes place with each of their composed objects, recursively.
|
20
|
+
#
|
21
|
+
# The typical usage: <tt>diff(x, y)</tt> returns an array representing the first difference
|
22
|
+
# found, like <tt>['age: 31', 'age: 84']</tt>. This is designed to be output with +puts+
|
23
|
+
# in +irb+, so they appear one string per line.
|
24
|
+
#
|
25
|
+
# The +flags+ modify the result. Only one flag is currently observed. If it's a number,
|
26
|
+
# say 3, then the third difference, in alphabetical order, is returned. If it's
|
27
|
+
# <tt>:all</tt>, then all differences are returned in an array of arrays. If it's
|
28
|
+
# <tt>:n</tt>, then the number of differences is returned.
|
29
|
+
#
|
30
|
+
# == Examples
|
31
|
+
#
|
32
|
+
# ...
|
33
|
+
#
|
34
|
+
def diff(o1, o2, *flags)
|
35
|
+
case (flag = flags.first || 1)
|
36
|
+
when :n
|
37
|
+
op = :count
|
38
|
+
n = -1
|
39
|
+
when :all
|
40
|
+
op = :find_all
|
41
|
+
n = -1
|
42
|
+
when Numeric
|
43
|
+
op = :find_one
|
44
|
+
n = flag.to_i
|
45
|
+
n = 1 if n < 1
|
46
|
+
else
|
47
|
+
raise ArgumentError, flag.inspect
|
48
|
+
end
|
49
|
+
diff = catch(:found) do
|
50
|
+
diffs = _diff(o1, o2, '', n)
|
51
|
+
case op
|
52
|
+
when :find_one
|
53
|
+
return nil # If we get this far, no difference was found.
|
54
|
+
when :find_all
|
55
|
+
return diffs
|
56
|
+
when :count
|
57
|
+
return diffs.size
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return diff # This was thrown from _diff; it's a single result.
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
#
|
66
|
+
# +o1+ and +o2+ are the objects being compared. +base+ is the base name for the fields so
|
67
|
+
# far (so we can report the full path of the field name, like 'person.name.first'). +n+ is
|
68
|
+
# the number of differences we should skip before immediately returning the one we're
|
69
|
+
# after.
|
70
|
+
#
|
71
|
+
# Each time we find a difference, we add it to +diffs+ and decrement n. If n is zero, we
|
72
|
+
# have found the difference we were looking for so we throw :found and the difference (a
|
73
|
+
# 2-tuple).
|
74
|
+
#
|
75
|
+
# We return an array of all the differences we have found.
|
76
|
+
#
|
77
|
+
def _diff(o1, o2, base, n)
|
78
|
+
if _immediate?(o1) or _immediate?(o2)
|
79
|
+
return "Immediate value(s)"
|
80
|
+
#return [o1,o2].map { |o| "#{field_path}: #{o.inspect}" }
|
81
|
+
else
|
82
|
+
# Both objects are complex, and we're happy.
|
83
|
+
h1 = _hash_representation(o1)
|
84
|
+
h2 = _hash_representation(o2)
|
85
|
+
fields = (h1.keys + h2.keys).uniq.sort
|
86
|
+
diffs = []
|
87
|
+
fields.each do |f|
|
88
|
+
v1 = h1[f]
|
89
|
+
v2 = h2[f]
|
90
|
+
goirb binding if $X
|
91
|
+
if v1 == v2
|
92
|
+
next
|
93
|
+
else
|
94
|
+
# We've found two unequal values. If they're immediate values,
|
95
|
+
# we count it as a difference. If they're complex, then we
|
96
|
+
# recurse into them.
|
97
|
+
field_path = [base, f].reject { |s| s.empty? }.join('.')
|
98
|
+
if _immediate?(v1) or _immediate?(v2)
|
99
|
+
diff = [v1,v2].map { |v| "#{field_path}: #{v.inspect}" }
|
100
|
+
throw :found, diff if (n -= 1).zero?
|
101
|
+
diffs << diff
|
102
|
+
else
|
103
|
+
diffs.concat _diff(v1, v2, field_path, n)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return diffs
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Returns true iff the given object is an "immediate value". That means a fundamenal Ruby
|
113
|
+
# data type. It's an arbitrary definition, and the choices are hardcoded. Hopefully I can
|
114
|
+
# think of a better heuristic.
|
115
|
+
#
|
116
|
+
def _immediate?(object)
|
117
|
+
[Numeric, String, Range, Array, Hash, NilClass, TrueClass,
|
118
|
+
FalseClass].any? { |klass| klass === object }
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Returns a hash representation of an object, mapping field names to values.
|
123
|
+
#
|
124
|
+
def _hash_representation(object)
|
125
|
+
_methods = object.public_methods(false)
|
126
|
+
if Struct === object
|
127
|
+
# In a Struct, if X is an attribute, then there are methods X and X=.
|
128
|
+
fields = _methods.grep(/=/).map { |meth| meth.to_s.tr('=', '') }
|
129
|
+
fields = fields.select { |meth| _methods.include? meth }
|
130
|
+
else
|
131
|
+
# In a general complex oject, if X in an attribute, then there is a method X and an
|
132
|
+
# instance variable @X.
|
133
|
+
_variables = object.instance_variables.map { |v| v.tr('@', '') }
|
134
|
+
fields = _variables.select { |v| _methods.include? v }
|
135
|
+
end
|
136
|
+
# Now we've got the fields, which in all cases are public methods. So we call them to
|
137
|
+
# get the values and construct our hash.
|
138
|
+
result = {}
|
139
|
+
fields.each do |field|
|
140
|
+
result[field] = object.send(field)
|
141
|
+
end
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Implementation notes for diff.
|
147
|
+
#
|
148
|
+
# Approach:
|
149
|
+
# * we don't dig into arrays or hashes, etc.; those are "immediate" values along
|
150
|
+
# with Numeric, String, Range, etc.
|
151
|
+
# * if object under inspection is immediate, we compare with 'equal?' and that's it
|
152
|
+
# * OK, so we've got two complex objects
|
153
|
+
# * get hash representations, one level deep: field => value
|
154
|
+
# * there is special logic for Structs
|
155
|
+
# * if field sets are different, do we care?
|
156
|
+
# * we can just report that field F is X in o1 but is nil in o2
|
157
|
+
# * examine fields in alphabetical order
|
158
|
+
# * compare values (with #equal?)
|
159
|
+
# * if equal, go to next field
|
160
|
+
# * if not equal
|
161
|
+
# * if value(s) are immediate, compare and report (or skip, or count,
|
162
|
+
# according to flags)
|
163
|
+
# * otherwise, dig into those values (recurse)
|
164
|
+
#
|
165
|
+
|
166
|
+
# Alternative notes for diff.
|
167
|
+
#
|
168
|
+
# Approach:
|
169
|
+
# * have a separate method 'structure(obj)', which returns the structure of the
|
170
|
+
# given object; e.g. structure(person) -> ['name', 'age', 'address.number',
|
171
|
+
# 'address.road', 'address.city']
|
172
|
+
# * that 'structure' method would be useful for some other things
|
173
|
+
# * you can yield each bit as it's done
|
174
|
+
# * use 'structure' and 'eval' to get the value of things
|
175
|
+
# * be smart about nil values; e.g. person.address could be nil, which
|
176
|
+
# would represent a different, but compatible, structure
|
177
|
+
#
|
178
|
+
# This seems simpler than mixing it all up as in the implementation above.
|
179
|
+
|
180
|
+
end # module Kernel
|
181
|
+
|
182
|
+
class Object
|
183
|
+
def topology
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
=end
|