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.
@@ -0,0 +1,253 @@
1
+
2
+ !header dev-utils/test main
3
+
4
+ h1. Unit Test Organisation
5
+
6
+ !toc
7
+
8
+ <div class="planned">
9
+
10
+ (Everything in this section is _planned_ functionality.)
11
+
12
+ h2. Introduction
13
+
14
+ Unit testing can be a pain to organise.
15
+ * You have to tell your test code where to find the library code that it's
16
+ testing.
17
+ * You need a mechanism for running all your tests at once, but would also like
18
+ to be able to run just one, or a few.
19
+ * If the tests need data, you need to organise that as well. Sometimes you
20
+ need to work on a copy of the data so you don't muck it up for the next
21
+ test run.
22
+
23
+ All of these organisational hassles are dealt with by the @dev-utils/test@
24
+ package. It provides a method for running your test suite (specifying which
25
+ tests if you like), which manages the load path. It also provides a method
26
+ for accessing test data in a flexible way. These only work if you follow some
27
+ conventions (detailed below). So the final benefit provided by
28
+ @dev-utils/test@ is a documented approach to managing the unit testing
29
+ part of any project.
30
+
31
+ The whole point of @dev-utils/test@ is to collect a certain unit testing
32
+ organisation methodology in one place, and make that reusable across projects.
33
+ This is better than having to roll your own on each new project, usually
34
+ without a clear strategy.
35
+
36
+ This document contains:
37
+ * the rules for using @dev-utils/test@ with your project;
38
+ * the @dev-utils/test@ approach in detail;
39
+ * the ground rules to follow so @dev-utils/test@ can work with your project;
40
+ * configuration options in case you want to vary some things;
41
+ * information about the complete sample project included in the distribution.
42
+
43
+ p(comment). I'll probably forget about a "complete sample project". It should
44
+ be easy enough to use without that. Perhaps refer to a live project that uses
45
+ it.
46
+
47
+
48
+ h2. A @dev-utils/test@ Project: Summary
49
+
50
+ Relative to your project's root directory:
51
+
52
+ * Library code goes under @lib/@. So when a unit test does @require
53
+ 'projX/init'@, it will be @lib/projX/init.rb@ that is loaded.
54
+
55
+ * Unit test files are named, for example, @tc_projX.rb@, and go in @test/@ or
56
+ a subdirectory.
57
+
58
+ * You have a master test file, @test/TEST.rb@, which loads and runs some or
59
+ all of your unit tests. Writing this file is extremely easy, as
60
+ @dev-utils/test@ does all the hard work.
61
+
62
+ ** Running @ruby test/TEST.rb@ is the *only* way to run your unit tests.
63
+
64
+ ** This program gives you options over which tests are run and how much output
65
+ is given.
66
+
67
+ * Test data (files containing data that are used for unit tests) is located in
68
+ @test/DATA@ or a subdirectory. Unit tests use a method called @load_data@
69
+ to access this test data.
70
+
71
+ h3. Using Rake to Run Tests
72
+
73
+ Pursuant to the running of unit tests via @test/TEST.rb@, here is a Rake task
74
+ you can set up to run your tests for you.
75
+
76
+ <pre>
77
+ desc "Run unit tests"
78
+ task :test do
79
+ exec "ruby test/TEST.rb #{ARGV.join(' ')}"
80
+ end
81
+ </pre>
82
+
83
+ If that is in your Rakefile, and the Rakefile is in the project's root
84
+ directory, then you can run @rake test@ from any directory in the project, and
85
+ your unit tests will run. There are various command-line options you can give
86
+ to @test/TEST.rb@, as the next section will detail.
87
+
88
+
89
+ h2. A @dev-utils/test@ Project: Detail
90
+
91
+ To make use of @dev-utils/test@, this is how you operate. All your
92
+ unit tests are coded in files like @tc_<i>foo</i>.rb@ in the @test@
93
+ directory, or subdirectories. There is one master test file
94
+ @test/TEST.rb@, which will load and run all of the unit test files.
95
+ Thanks to this package, however, writing that file is easy.
96
+
97
+ This is what happens when you run @ruby test/TEST.rb --help@:
98
+
99
+ <pre style="font-size:10pt"> Welcome to the dev-utils/test automatic unit test runner. Here are the
100
+ options:
101
+
102
+ -q quiet mode
103
+ -Q very quiet mode
104
+ -v verbose mode (default)
105
+ -s run unit tests in separate test suites
106
+ -h show help
107
+ str1 str2 ... run only those tests whose filenames contain str1 or
108
+ str2 or ...
109
+
110
+ For example, to run only those tests whose filenames include "remote" or
111
+ "db", and to run a separate suite for each test file, and to run in quiet
112
+ mode:
113
+
114
+ ruby test/TEST.rb -q remote db -s
115
+ </pre>
116
+
117
+ As the developer, you don't need to do any programming to make this happen;
118
+ you just need to create @test/TEST.rb@, in which you call one method.
119
+ This is what that file looks like:
120
+
121
+ <pre>
122
+ require 'rubygems'
123
+ require 'dev-utils/test'
124
+
125
+ DevUtils::Test.load_tests(__FILE__, *ARGV)
126
+ </pre>
127
+
128
+ As a sneak peek behind the scenes, @load_tests@ uses <code>__FILE__</code> to
129
+ orient itself, using default values to locate the @lib@ directory (so it can
130
+ manipulate the Ruby load path), and the @DATA@ directory (so calls to
131
+ @load_data@ will work).
132
+
133
+ So far, we have covered two of the three organisational hassles listed in the
134
+ introduction. The remaining one is managing test data. This is handled by
135
+ the @load_data@ method.
136
+
137
+ All your test data is contained in files underneath the @test/DATA@
138
+ directory. You use the path to those files (relative to @test/DATA@)
139
+ to identify the data file you want to load. Simple enough. So a unit test
140
+ for an email reader might look like this:
141
+
142
+ <pre>
143
+ def test_sender_extraction
144
+ input = Test.load_data('bulk/emails.txt')
145
+ output = EmailParser.parse(input)
146
+ assert_equal(...)
147
+ end
148
+ </pre>
149
+
150
+ In that case, the data in the @test/DATA/bulk/emails.txt@ file was served up
151
+ as a String. But @load_data@ is more flexible than that. It defaults to
152
+ returning a String, but can return a File (opened for reading) or a Pathname
153
+ instead. Also, it can make a copy of the file (to a temporary place) and
154
+ point you to that.
155
+
156
+ So a unit test that is modifying an SQLite database might look like this:
157
+
158
+ <pre>
159
+ def test_database_modification
160
+ dbfile = Test.load_data('customers3.db', :pathname, :copy)
161
+ init_system(dbfile)
162
+ ...
163
+ end
164
+ </pre>
165
+
166
+ The above code uses <i>@Test@</i> instead of <i>@DevUtils::Test@</i>
167
+ because it is assumed that an @include DevUtils@ was performed in the
168
+ test class.
169
+
170
+
171
+ h3. Consequences
172
+
173
+ The consequence of writing unit tests using @dev-utils/test@ is that they
174
+ cannot be run directly. Unit tests are written _assuming_ that they can find
175
+ the right library file in the load path. Without being run through
176
+ @test/TEST.rb@, this assumption does not (necessarily) hold.
177
+
178
+ And if a unit test calls @load_data@ without the initialization step that
179
+ @load_tests@ provides (or without explicit initialization - see below), then
180
+ that data will not be found.
181
+
182
+
183
+ h2. Ground Rules
184
+
185
+ <b><i>The information here has been effectively stated in the Summary section
186
+ above. Get rid of this.</i></b>
187
+
188
+ In order to benefit from these unit testing features, you must follow some
189
+ conventions in the way you organise your project and write your code. If your
190
+ project is called @projectX@, then this is how it should be organised:
191
+
192
+ * The unit tests themselves are named @tc_<i>foo</i>.rb@, and are
193
+ located under the @projectX/test@ directory. The name of that
194
+ directory doesn't matter, but we're assuming that for the following points.
195
+
196
+ * The master test runner is @projectX/test/TEST.rb@. Unit tests are
197
+ *always* run through this program. It contains the code shown in the
198
+ previous section.
199
+
200
+ * The library code goes in @projectX/lib/...@. Observing this
201
+ convention means that @load_tests@ knows where to find the library code that
202
+ is being tested.
203
+
204
+ * If the tests make use of data files, those files are located in the
205
+ @projectX/test/DATA@ directory. This way, the default will work and
206
+ the @load_tests@ method above will set the data directory correctly.
207
+
208
+ * ...
209
+
210
+
211
+ h2. Configurations
212
+
213
+ If you want the test data directory to be something other than
214
+ @test/DATA@, or you want the lib directory to be something other than
215
+ @lib@ (both relative to the root directory of the project), then the
216
+ following methods are available to you:
217
+ * @DevUtils::Test.project_dir = ...@
218
+ * @DevUtils::Test.set_project_dir_from(path, project_name)@
219
+ * @DevUtils::Test.lib_dir = ...@
220
+ * @DevUtils::Test.test_dir = ...@
221
+ * @DevUtils::Test.data_dir = ...@
222
+
223
+ If you want to specify any of them, then you *must* specify the project
224
+ directory first, as all the others are interpreted relative to that. Also,
225
+ these directories must be specified _before_ you call @load_tests@. Really,
226
+ the only appropriate place for any of this is @test/TEST.rb@.
227
+
228
+ p. @set_project_dir_from@ is the easier way to set the project directly. If you
229
+ use @project_dir=@ instead, _make sure_ you specify an absolute path (_I think
230
+ that requirement still stands..._).
231
+
232
+ Here is an example of a @unit-tests/run_tests.rb@ file with full
233
+ customisation. The name of the project is @projectX@.
234
+
235
+ <pre>
236
+ require 'rubygems'
237
+ require 'dev-utils/test'
238
+ include DevUtils
239
+
240
+ Test.set_project_dir_from(__FILE__, "projectX")
241
+ Test.lib_dir = "."
242
+ Test.test_dir = "unit-tests"
243
+ Test.data_dir = "etc/test-data"
244
+
245
+ Test.load_tests(__FILE__, *ARGV)
246
+ </pre>
247
+
248
+
249
+ h2. A Complete Sample Project
250
+
251
+ p(comment). Not bloody likely.
252
+
253
+ </div>
@@ -0,0 +1,187 @@
1
+ #
2
+ # = generate.rb, for the ruby-journal project.
3
+ #
4
+ # Documents are written in Textile and converted to HTML with RedCloth.
5
+ #
6
+ # However, RedCloth only generates HTML _fragments_. We need to generate HTML _documents_,
7
+ # with <tt><head></tt> information (especially CSS), etc.
8
+ #
9
+ # The simple function +generate_document+ does this.
10
+ #
11
+
12
+ require 'rubygems'
13
+ require 'redcloth'
14
+ require 'extensions/io'
15
+ require 'celsoft.com/template'
16
+
17
+ #
18
+ # Takes the Textile file from +path+ (relative to ~/Projects/dev-utils) and generates an HTML
19
+ # file in the appropriate place, which is the <tt>build/www</tt> directory, and with an +html+
20
+ # instead of +textile+ extension.
21
+ #
22
+ # The HTML document will contain an inline stylesheet.
23
+ #
24
+ # The first "h1." tag in the document is used as the title of the HTML page.
25
+ #
26
+ # Special lines !header and !toc are expanded.
27
+ #
28
+ def generate_document(input_path, output_dir)
29
+ output_path = File.join(output_dir, "#{File.basename(input_path)}".sub(/\.textile/, ".html"))
30
+ raw_text = File.read(input_path)
31
+ #trace {'input_path'}
32
+ #trace { 'raw_text.length' }
33
+ input_text = process_text(raw_text)
34
+ red_cloth_text = RedCloth.new(input_text)
35
+ red_cloth_text.fold_lines = true
36
+ html_fragment = red_cloth_text.to_html
37
+ html = html_wrap(html_fragment)
38
+ File.write(output_path, html)
39
+ #File.write(output_path + '.tmp', input_text)
40
+ end
41
+
42
+ #
43
+ # Expand any meta-markers like !header and !toc.
44
+ #
45
+ def process_text(text)
46
+ require 'shellwords'
47
+ text.gsub(/^!(.*)\s*$/) {
48
+ command, *args = Shellwords.shellwords($1.strip)
49
+ case command
50
+ when 'header' then header(*args)
51
+ when 'toc'
52
+ _toc = toc(text)
53
+ _toc
54
+ else
55
+ raise "Unknown command embedded in textile input: #{command}"
56
+ end
57
+ }
58
+ end
59
+
60
+ CSS_FILE = 'etc/doc/textile.css'
61
+
62
+ #
63
+ # Wraps the given HTML fragment in head, body, etc., to produce a proper document.
64
+ #
65
+ # It points to a hardcoded stylesheet.
66
+ #
67
+ def html_wrap(fragment)
68
+ require 'extensions/string'
69
+ title = fragment.scan(%r{<h1>(.*?)</h1>}).first || []
70
+ title = title.first || "(no title)"
71
+ return %{
72
+ <html>
73
+ <head>
74
+ <title>#{title}</title>
75
+ <style type="text/css">
76
+ #{File.read(CSS_FILE).tabto(6)}
77
+ </style>
78
+ </head>
79
+ <body>
80
+ #{fragment}
81
+ </body>
82
+ </html>
83
+ }.tabto(0)
84
+ end
85
+
86
+ def header(*args)
87
+ require_arg = args.shift or raise ArgumentError
88
+ require_text = "%(small-title)<code>require '#{require_arg}'</code>%"
89
+ if args.shift == 'main'
90
+ main_page_link = '%(mylink)"main page":index.html%'
91
+ else
92
+ main_page_link = ''
93
+ end
94
+ %!table{width:100%}.\n| #{require_text} |>. #{main_page_link} |\n\n!
95
+ end
96
+
97
+ TOC_TEMPLATE = Template::Document.new
98
+ TOC_TEMPLATE.load %!
99
+ <table class="toc_table">
100
+ <tr>
101
+ <td>
102
+ <span class="header">Links</span><br/>
103
+ <ul id="links">
104
+ ${each links}
105
+ <li><a href="${var href}">${var name}</a></li>
106
+ ${end}
107
+ </ul>
108
+ </td>
109
+ <td>
110
+ <span class="header">Contents</span><br/>
111
+ <ul id="contents">
112
+ ${each contents}
113
+ <li><a href="#\${var anchor}">${var heading}</a>
114
+ ${if subheadings}
115
+ <ul>
116
+ ${each subheadings}
117
+ <li><a href="#\${var anchor}">${var heading}</a></li>
118
+ ${end}
119
+ </ul>
120
+ </li>
121
+ ${end}
122
+ ${end}
123
+ </ul>
124
+ </td>
125
+ </tr>
126
+ </table>
127
+ !
128
+
129
+ Heading = Struct.new(:level, :heading, :anchor)
130
+
131
+ # Returns the HTML for a table containing links (from links.dat) and the contents
132
+ # of this page, derived from the given text. h2 and h3 markup elements are taken
133
+ # to be first- and second-level headings in the text. The text itself is modified
134
+ # so that those headings have anchors on them.
135
+ def toc(text)
136
+ # Form links: [ { 'name' => 'Synopsis', 'href' => 'Synopsis.html' }, ... ]
137
+ links = links().map { |name, href|
138
+ Hash[ 'name', name, 'href', href ]
139
+ }
140
+
141
+ # Form contents: [
142
+ # { 'heading' => 'Introduction', 'anchor' => 'Introduction', 'subheadings' => [ { ... } ] },
143
+ # ...
144
+ # ]
145
+ regex = /^h([234])\.\s+(.*?)$/
146
+ headings = text.grep(regex).map { |line|
147
+ line =~ regex
148
+ level, heading = $1.to_i - 1, $2.strip
149
+ anchor = heading.gsub(/[^\s\w]/, '')
150
+ #Hash[ 'level', level, 'heading', heading, 'anchor', anchor ]
151
+ Heading.new(level, heading, anchor)
152
+ }
153
+ contents = []
154
+ headings.each do |h|
155
+ case h.level
156
+ when 1
157
+ contents << Hash['heading', h.heading, 'anchor', h.anchor]
158
+ when 2
159
+ (contents.last['subheadings'] ||= []) << Hash['heading', h.heading, 'anchor', h.anchor]
160
+ end
161
+ end
162
+
163
+ # Place anchors into document. This changes the input text.
164
+ text.gsub!(regex) {
165
+ match = Regexp.last_match
166
+ heading = $2.strip
167
+ anchor = heading.gsub(/[^\s\w]/, '')
168
+ match[0].chomp + %{ <a name="#{anchor}"/>\n}
169
+ }
170
+
171
+ # Run it through the template.
172
+ TOC_TEMPLATE.data = { 'links' => links, 'contents' => contents }
173
+ TOC_TEMPLATE.output
174
+ end
175
+
176
+ # Return [ [name, href], [name, href], ... ].
177
+ def links
178
+ @links ||=
179
+ begin
180
+ links_file = Pathname.new(__FILE__).dirname + 'links.dat'
181
+ File.readlines(links_file).grep(/^\w/).map { |line|
182
+ line =~ %r{^(.*?)/(.*)$} or raise "Bad data in links.dat."
183
+ [$1.strip, $2.strip]
184
+ }
185
+ end
186
+ end
187
+
@@ -0,0 +1,110 @@
1
+ !header dev-utils nomain
2
+
3
+ h1. The Ruby dev-utils Project
4
+
5
+ !toc
6
+
7
+ h2. About dev-utils
8
+
9
+ p. @dev-utils@ provides utilites to assist the process of developing Ruby
10
+ programs. At the moment, the target areas are debugging and unit testing
11
+ (planned).
12
+
13
+ Version 1.0 was released on 2004-10-08 as a RubyGem. RPA and tarball
14
+ installation will be provided shortly in 1.0.1. Throughout this
15
+ documentation, there are references to planned features. These will be
16
+ released in 2.0, for which there is no estimated completion date.
17
+
18
+ h3. Debugging
19
+
20
+ With @dev-utils/debug@ you can:
21
+
22
+ * Escape to an IRB session from a running program.
23
+
24
+ <pre style="margin-left:5em">
25
+ breakpoint
26
+ breakpoint 'Person#name' # Identify it when it happens.
27
+ breakpoint { @name } # Default return value.
28
+ </pre>
29
+
30
+ * Access a no-config logfile for debugging.
31
+
32
+ <pre style="margin-left:5em">
33
+ debug 'Database connection established' # Look in ./debug.log
34
+ </pre>
35
+
36
+ * Trace expressions in that logfile.
37
+
38
+ <pre style="margin-left:5em">
39
+ trace 'x + y'
40
+ trace 'Process.pid'
41
+ trace 'names', :pp # Pretty-print.
42
+ trace 'page_structure', :yaml # YAML representation.
43
+ </pre>
44
+
45
+ * %(planned)Find the difference between complex objects (planned).%
46
+
47
+ For more information, see "Debugging Aids":DebuggingAids.html.
48
+
49
+ <div class="planned">
50
+
51
+ h3. Unit Testing (Planned)
52
+
53
+ With @dev-utils/test@ you can:
54
+ * Run some or all of your unit tests, automatically finding the library code.
55
+ * Load test data from a separate data directory.
56
+
57
+ For more information, see "Unit Test Organisation":UnitTestOrganisation.html.
58
+
59
+ </div>
60
+
61
+
62
+ h2. Internet Links
63
+
64
+ |{width:25ex}. Home page: | "http://dev-utils.rubyforge.org":home |
65
+ | Project page: | "http://rubyforge.org/projects/dev-utils":project |
66
+ | Download: | "http://rubyforge.org/frs/?group_id=270":download |
67
+ | API Documentation: | "http://dev-utils.rubyforge.org/api":api |
68
+
69
+ [home]http://dev-utils.rubyforge.org
70
+ [project]http://rubyforge.org/projects/dev-utils
71
+ [download]http://rubyforge.org/frs/?group_id=270
72
+ [api]http://dev-utils.rubyforge.org/api
73
+
74
+
75
+ h2. Administrivia
76
+
77
+ h3. Credits
78
+
79
+ The breakpoint-related code was contributed unwittingly by Joel VanderWerf and
80
+ Florian Gross. The rest of the code is by Gavin Sinclair.
81
+
82
+ h3. Notes
83
+
84
+ p. @dev-utils@ depends on "@extensions@":http://extensions.rubyforge.org, version
85
+ 0.5 or greater.
86
+
87
+ Runnable examples are located in the @examples@ directory of the distribution.
88
+
89
+ h3. License
90
+
91
+ Copyright (c) 2004, Gavin Sinclair.
92
+
93
+ Permission is hereby granted, free of charge, to any person obtaining a copy
94
+ of this software and associated documentation files (the "Software"), to deal
95
+ in the Software without restriction, including without limitation the rights
96
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
97
+ copies of the Software, and to permit persons to whom the Software is
98
+ furnished to do so, subject to the following conditions:
99
+
100
+ The above copyright notice and this permission notice shall be included in all
101
+ copies or substantial portions of the Software.
102
+
103
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
104
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
105
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
106
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
107
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
108
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
109
+ SOFTWARE.
110
+