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
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
|