blackstart 0.1.0 → 0.5.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/README.txt CHANGED
@@ -2,151 +2,175 @@ Blackstart
2
2
 
3
3
  Blackstart is a small, subdued library for automated testing in Ruby. It
4
4
  doesn't depend on anything beyond the Ruby platform and doesn't modify the
5
- environment outside its conventional namespace. It's tested with a primitive
5
+ environment beyond its conventional namespace. It's tested with a primitive
6
6
  program, not via an automated-testing library.
7
7
 
8
- The blackstart is a small, subdued bird that eats bugs. A black start is when a
9
- power plant starts up without relying on the electrical grid.
8
+ The blackstart is a small, subdued bird that eats bugs. A black start is when
9
+ an inactive power plant restarts by means of an independent power source rather
10
+ than the electrical grid.
10
11
 
11
12
  Here's an example of a test program that uses Blackstart:
12
13
 
13
14
  require "blackstart"
14
15
 
15
- exit Blackstart.run Blackstart.add { |adder|
16
- adder.call do
16
+ exit Blackstart.run [
17
+ proc {
17
18
  unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
18
19
  raise "join did not work as expected"
19
20
  end
20
- end
21
+ },
21
22
 
22
- adder.call do
23
+ proc {
23
24
  unless "sample" == "simple".gsub(/i/, "a")
24
25
  raise "gsub did not work as expected"
25
26
  end
26
- end
27
- }
28
-
29
- Blackstart.add adds procs to a collection and returns that collection. In the
30
- example, each proc is designed to run a test and, if it fails, raise an error
31
- to signal this.
27
+ }
28
+ ]
32
29
 
33
30
  Blackstart.run runs a sequence of tests and reports information about any that
34
- fail -- that is, any that raise an error. In the example, failure information
35
- would be written to the standard output stream. It returns false if there were
36
- failures; otherwise, it returns true.
31
+ fail. It returns false if there were failures, true otherwise.
32
+
33
+ An array of procs is an easy way to represent a sequence of tests. In general,
34
+ the sequence can be any object that responds to an each message in the
35
+ conventional way and each test object in the sequence can be any object that
36
+ (1) is a proc or converts to one via to_proc and (2) converts to a string via
37
+ to_s.
38
+
39
+ Each test is run by converting the test object to a proc if necessary and then
40
+ calling it in the context of a new instance of Blackstart::Scratchpad; if and
41
+ only if this raises an error (any exception that's a kind of StandardError),
42
+ Blackstart.run interprets it as a failed test.
43
+
44
+ After each test failure, Blackstart.run sends a puts message with failure
45
+ information to its second parameter, which is $stdout by default. The failure
46
+ information includes a description of the test (the test object converted to a
47
+ string) and a description of the error raised (its class, message, and
48
+ backtrace).
37
49
 
38
- The library provides little beyond this, but it's easy to do relatively
39
- sophisticated things with it. Some examples follow.
50
+ The library provides little beyond this, but it's easy to do sophisticated
51
+ things with it. Some examples follow.
40
52
 
41
53
 
42
54
  - Exiting with the appropriate status
43
55
 
44
- Blackstart.run returns false if there were any failures; otherwise, it returns
45
- true. If you use this as the argument to Kernel#exit, your test program will
46
- exit with a successful status only if there were no failures.
56
+ You may want your test program to exit with a successful status only if there
57
+ were no failures, perhaps so it can work as part of a testing script. You can
58
+ do this by using the object returned by Blackstart.run as the argument to
59
+ Kernel#exit. Blackstart.run returns false if there were failures, true
60
+ otherwise.
47
61
 
48
62
 
49
63
  - Defining helpers
50
64
 
51
- You may want all your tests to be able to assert something, generate test data,
65
+ You may want to enable all your tests to assert something, generate test data,
52
66
  or perform some other task by sending a message. You could implement this
53
67
  statelessly in a singleton object or all instances of Object, for example, but
54
- there's another option that may be more convenient. Blackstart.run calls each
55
- test proc in the context of a new Blackstart::Context instance. You can define
56
- instance methods in that class like this:
68
+ there's another option that may be more convenient. When Blackstart.run calls a
69
+ test proc, it sets the self object to a new instance of Blackstart::Scratchpad.
70
+ You can define instance methods in that class. For example:
57
71
 
58
- class Blackstart::Context
72
+ class Blackstart::Scratchpad
59
73
  def assert boolean
60
74
  raise "assertion failed" unless boolean
61
75
  end
62
76
 
63
77
  def make_products
64
78
  @cabbage = { :description => "head of cabbage", :price => 125 }
65
- @orange = { :description => "Cara cara navel orange", :price => 100 }
79
+ @orange = { :description => "Cara Cara navel orange", :price => 100 }
66
80
  nil
67
81
  end
68
82
  end
69
83
 
70
- All of your tests will be able to use them by sending messages to self. These
71
- methods can see and modify the test's state, which is disposable: it will be
72
- discarded after the test is complete and so will not affect later tests.
84
+ All your test procs can use them by sending messages to self. These methods,
85
+ like the test proc itself, can see and modify instance variables and other
86
+ elements of the scratchpad's state. The scratchpad is disposable:
87
+ Blackstart.run discards it after the test is complete.
73
88
 
74
89
 
75
90
  - Running code before and after each test
76
91
 
77
- You can run code before and after each test by defining a custom
78
- Blackstart::Context#instance_exec. For example:
92
+ You may want to run code before and after each test -- for example, to create
93
+ objects needed in tests or clean up resources without requiring every test to
94
+ do these explicitly. You can do this by defining a custom
95
+ Blackstart::Scratchpad#instance_exec. For example:
79
96
 
80
- class Blackstart::Context
97
+ class Blackstart::Scratchpad
81
98
  def instance_exec(*)
82
- puts "doing setup"
99
+ puts "before test"
83
100
  @variable = "example"
84
101
  super
85
102
  ensure
86
- puts "doing teardown"
103
+ puts "after test"
87
104
  end
88
105
  end
89
106
 
107
+ Use this hack with care because the test proc could send instance_exec messages
108
+ to self.
109
+
90
110
 
91
111
  - Building a test collection in stages
92
112
 
93
- You can build your collection of test objects in stages and run it at the end.
94
- For example:
113
+ You may want to build a test collection in stages rather than all at once. This
114
+ can be done straightforwardly:
95
115
 
96
116
  require "blackstart"
97
117
 
98
- TEST_OBJECTS = []
118
+ TESTS = []
99
119
 
100
- Blackstart.add TEST_OBJECTS do |adder|
101
- adder.call do
120
+ TESTS.concat [
121
+ proc {
102
122
  unless "Hello, World!" == ["He", "", "o, Wor", "d!"].join("l")
103
123
  raise "join did not work as expected"
104
124
  end
105
- end
106
- end
125
+ }
126
+ ]
107
127
 
108
- Blackstart.add TEST_OBJECTS do |adder|
109
- adder.call do
128
+ TESTS.concat [
129
+ proc {
110
130
  unless "sample" == "simple".gsub(/i/, "a")
111
131
  raise "gsub did not work as expected"
112
132
  end
113
- end
114
- end
133
+ }
134
+ ]
115
135
 
116
- exit Blackstart.run TEST_OBJECTS
136
+ exit Blackstart.run TESTS
117
137
 
118
- This makes it straightforward to define your tests in multiple files that you
119
- load in your test program.
138
+ This pattern is useful if you want to define your tests in multiple files: you
139
+ create a collection with an agreed-upon name, load multiple files, each of
140
+ which adds test objects to that collection, and then run all the tests.
120
141
 
121
142
 
122
143
  - Running tests in random order
123
144
 
145
+ To check if you have any tests that depend on other tests having run, or not
146
+ having run, earlier, you may want to run your tests in random order.
124
147
  Blackstart.run runs a sequence of tests in order, but you can pass it a
125
- randomly-ordered sequence. Array#shuffle may be helpful for this. If you do
126
- this, you may also want to print the random seed at the start of your program
127
- so you can re-run your tests in the same order.
148
+ randomly-ordered sequence. Array#shuffle may be helpful for this. If you use
149
+ Array#shuffle or something similar, you may also want to print the random seed
150
+ just before the tests are shuffled so you can rerun your tests in the same
151
+ order by setting the random seed.
128
152
 
129
153
 
130
154
  - Reporting detailed test descriptions
131
155
 
132
- After a test fails, Blackstart.run writes a description of the test to the
133
- output stream. It gets this description by converting the test object to a
134
- string. When the test object is an instance of Proc, which is what
135
- Blackstart.add produces when used as expected, this string typically includes
136
- the file path and line number where it was defined: helpful, but it won't be
137
- immediately clear what was being tested.
156
+ After a test fails, Blackstart.run writes a string describing the test to the
157
+ output stream. It gets this string by sending a to_s message to the test
158
+ object. When the test object is an instance of Proc, the returned string
159
+ typically includes the file path and line number where it was defined: helpful,
160
+ but it won't be immediately clear what was being tested.
138
161
 
139
- You can improve this by making your own test objects instead of using
140
- Blackstart.add. Blackstart.run does not strictly need a sequence of Proc
141
- instances; it only needs a sequence of objects that convert to Proc instances
142
- in response to to_proc messages. Your custom test objects can respond to to_s
143
- with detailed descriptions instead of mere file paths and line numbers.
162
+ You can improve this by designing your own test objects. Blackstart.run does
163
+ not strictly need a sequence of procs; it also works with a sequence of objects
164
+ that convert to procs in response to to_proc messages. Your custom test objects
165
+ can respond to to_s with detailed descriptions instead of mere file paths and
166
+ line numbers.
144
167
 
145
168
 
146
169
  - Handling failures differently
147
170
 
148
171
  Blackstart.run handles each failure by sending a puts message to the output
149
- stream, one of its parameters. By default, this is $stdout, so the default
150
- behavior is to print unadorned failure information to standard output. But you
151
- can specify any object as the output stream -- even if it's not really a stream
152
- -- allowing you to handle failure information however you want.
172
+ stream, one of its parameters, with failure information. By default, the output
173
+ stream is $stdout, so the default behavior is to print unadorned failure
174
+ information to standard output. But you can specify any object as the output
175
+ stream -- even if it's not really a stream -- allowing you to handle failure
176
+ information however you want.
data/blackstart.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "blackstart"
3
- s.version = "0.1.0"
3
+ s.version = "0.5.0"
4
4
  s.authors = ["Aaron Beckerman"]
5
5
  s.summary = "A small, subdued library for automated testing."
6
6
  s.licenses = ["MIT"]
data/lib/blackstart.rb CHANGED
@@ -1,22 +1,24 @@
1
1
  ##
2
- # This module provides facilities for defining and running automated tests.
2
+ # This module provides facilities for running automated tests.
3
3
 
4
4
  module Blackstart
5
5
 
6
6
  ##
7
- # Calls the block and returns a string describing the error raised, if any.
7
+ # Yields to the block and returns a string describing the error raised, if
8
+ # any.
8
9
  #
9
- # In detail: This method sends call with no arguments to the object
10
- # representing the block. If that raises an error -- that is, an exception
11
- # whose class is StandardError or a descendent of StandardError -- this
12
- # method creates a string describing that error and returns it; the
13
- # description includes the error's class, message, and backtrace (if any). If
14
- # sending the message raises any other exception, this method raises that
15
- # exception. If sending the message does not raise an exception, this method
16
- # returns nil.
10
+ # In detail: This method yields to the block with no arguments. If that
11
+ # raises an error -- meaning an exception whose class is StandardError or a
12
+ # descendant of StandardError -- this method attempts to make and return a
13
+ # string describing that error; the description consists of the error's
14
+ # class, message, and backtrace (if any). If an exception is raised while
15
+ # making an error description, this method raises that exception. If
16
+ # yielding to the block raises a non-error exception, this method raises that
17
+ # exception. If yielding to the block does not raise an exception, this
18
+ # method returns nil.
17
19
 
18
- def self.vet &prc
19
- prc.call
20
+ def self.vet
21
+ yield
20
22
  nil
21
23
  rescue ::StandardError
22
24
  # Like IO#puts, separate lines with a line feed character rather than $\.
@@ -24,64 +26,45 @@ module Blackstart
24
26
  end
25
27
 
26
28
  ##
27
- # Allows the block to add procs to a collection, which it returns.
28
- #
29
- # In detail: This method sends call with one positional argument, an adder
30
- # object, to the object representing the block, director. This block can send
31
- # call messages to the adder to add objects representing the respective
32
- # blocks to the collection, sink. With each sending of call, the object to be
33
- # added will be either a proc (if call is sent with a block) or nil (if call
34
- # is sent without a block). If any non-block arguments are sent with the call
35
- # message, an exception is raised. To add the object, the adder sends a <<
36
- # message to sink with the object as the positional argument and no other
37
- # arguments. The adder returns nil in response to a call message if it
38
- # returns at all. This method returns sink.
39
- #
40
- # By default, sink is a new empty array.
41
-
42
- def self.add sink = [], &director
43
- director.call ::Kernel.lambda { |&prc|
44
- sink << prc
45
- nil
46
- }
47
- sink
48
- end
49
-
50
- ##
51
- # A class for the contexts (the self objects) of tests.
29
+ # A class for tests' disposable helper objects.
52
30
 
53
- class Context
31
+ class Scratchpad
54
32
  end
55
33
 
56
34
  ##
57
- # Runs the tests, writes any failure information to the output stream, and
35
+ # Runs tests, writes any failure information to the output stream, and
58
36
  # returns a boolean indicating whether there were no failures.
59
37
  #
60
- # In detail: This method expects the sequence of test objects, source, to
61
- # respond to an each message by yielding successive test objects to its
62
- # block. Each test is run by converting the test object to a proc and calling
63
- # it in the context of a new instance of Blackstart::Context. A failure is
64
- # when the test raises an error (that is, an exception whose class is
65
- # StandardError or a descendent of StandardError). After a failure, this
66
- # method sends a puts message to the output stream, ostream, with these
67
- # arguments (positional, in order): a string "FAILED TEST:", the test object
68
- # converted to a string via to_s, a string "...ERROR:", a string describing
69
- # the error, and an empty string. If a test raises any other exception, this
70
- # method immediately raises that exception. After it has run all the tests
71
- # and handled any failures, this method returns false if there were failures
72
- # or true otherwise.
38
+ # In detail: This method sends an each message with a block to the sequence
39
+ # of test objects, source, expecting it to yield to the block once for each
40
+ # test object in the sequence with that test object as the first argument.
41
+ # Each test involves creating a new scratchpad -- an instance of
42
+ # Blackstart::Scratchpad -- and sending an instance_exec message to it with
43
+ # the test object as a block argument, which converts the test object to a
44
+ # proc via to_proc if it's not already a proc. The resulting proc is called
45
+ # with no arguments; the self object is set to the scratchpad. A failure is
46
+ # when the test (which includes any initial conversion to a proc) raises an
47
+ # error: an exception whose class is StandardError or a descendant of
48
+ # StandardError. After a failure, this method sends a puts message to the
49
+ # output stream, ostream, with these arguments (positional, in order): a
50
+ # string "FAILED TEST:"; the test object converted to a string via to_s; a
51
+ # string "...ERROR:"; a string combining the class, message, and backtrace of
52
+ # the error; and an empty string. If a test raises a non-error exception,
53
+ # this method immediately raises that exception. After it has run all the
54
+ # tests and handled any failures, this method returns false if there were
55
+ # failures, true otherwise. This method immediately raises any exception
56
+ # raised outside of a test.
73
57
  #
74
58
  # By default, ostream is $stdout.
75
59
 
76
60
  def self.run source, ostream = $stdout
77
61
  success = true
78
- for tst in source
79
- # Convert to a proc and create a context outside the vet block so that
80
- # this method will raise any exception that results instead of
81
- # potentially treating it as a test failure.
82
- prc = ::Proc.new(&tst)
83
- context = Context.new
84
- if err_desc = vet { context.instance_exec(&prc) }
62
+ source.each do |tst|
63
+ # Create the scratchpad outside the vet block so that this method will
64
+ # raise any exception that results instead of potentially treating it as
65
+ # a test failure.
66
+ scratchpad = Scratchpad.new
67
+ if err_desc = vet { scratchpad.instance_exec(&tst) }
85
68
  success = false
86
69
  ostream.puts "FAILED TEST:", tst.to_s, "...ERROR:", err_desc.to_s, ""
87
70
  end
@@ -6,9 +6,8 @@ require "blackstart"
6
6
  # Blackstart should be a module.
7
7
  fail "" unless Module.equal? Blackstart.class
8
8
 
9
- # Blackstart should say it responds to vet, add, and run.
9
+ # Blackstart should say it responds to vet and run.
10
10
  fail "" unless Blackstart.respond_to? :vet
11
- fail "" unless Blackstart.respond_to? :add
12
11
  fail "" unless Blackstart.respond_to? :run
13
12
 
14
13
  # Test Blackstart.vet:
@@ -27,10 +26,14 @@ else
27
26
  fail ""
28
27
  end
29
28
 
30
- # If sending call with no arguments to the block raises an error,
31
- # Blackstart.vet should return a new descriptive string.
29
+ # If yielding to the block raises an error, Blackstart.vet should return a
30
+ # string describing that error.
32
31
  class ::Object
33
- fail "" unless /\ANoMethodError: / =~ Blackstart.vet
32
+ # Should return an error description when no block is given.
33
+ fail "" unless String.equal? Blackstart.vet.class
34
+
35
+ # Use frozen strings and backtrace arrays so that an exception will be raised
36
+ # if they're modified.
34
37
 
35
38
  e_class = Class.new StandardError
36
39
  def e_class.to_s
@@ -41,164 +44,70 @@ class ::Object
41
44
  def e_nil.backtrace
42
45
  nil
43
46
  end
44
- fail "" unless "FakeError: a" == Blackstart.vet { ::Kernel.raise e_nil }
47
+ error = Blackstart.vet { ::Kernel.raise e_nil }
48
+ fail "" unless String.equal? error.class
49
+ fail "" unless "FakeError: a" == error
45
50
 
46
51
  e_empty = e_class.new "b".freeze
47
52
  def e_empty.backtrace
48
53
  [].freeze
49
54
  end
50
- fail "" unless "FakeError: b" == Blackstart.vet { ::Kernel.raise e_empty }
55
+ error = Blackstart.vet { ::Kernel.raise e_empty }
56
+ fail "" unless String.equal? error.class
57
+ fail "" unless "FakeError: b" == error
51
58
 
52
59
  e_full = e_class.new "c".freeze
53
60
  def e_full.backtrace
54
61
  ["d".freeze, "e".freeze].freeze
55
62
  end
56
- fail "" unless "FakeError: c\nd\ne" ==
57
- Blackstart.vet { ::Kernel.raise e_full }
58
- end
59
-
60
- # If sending call with no arguments to the block raises a non-StandardError
61
- # exception, Blackstart.vet should not rescue it.
62
- class ::Object
63
- non_standard_error = Class.new Exception
64
- begin
65
- Blackstart.vet { ::Kernel.raise non_standard_error }
66
- rescue non_standard_error
67
- else
68
- fail ""
69
- end
70
- end
71
-
72
- # If sending call with no arguments to the block does not raise an exception,
73
- # Blackstart.vet should return nil.
74
- fail "" unless nil.equal? Blackstart.vet { nil }
75
- fail "" unless nil.equal? Blackstart.vet { true }
76
-
77
- # Test Blackstart.add:
78
-
79
- # When there is no block, Blackstart.add should raise an exception.
80
- begin
81
- Blackstart.add
82
- rescue NoMethodError
83
- else
84
- fail ""
85
- end
86
- begin
87
- Blackstart.add []
88
- rescue NoMethodError
89
- else
90
- fail ""
91
- end
92
-
93
- # Blackstart.add should raise an exception if superfluous arguments are sent.
94
- begin
95
- Blackstart.add [], "invalid"
96
- rescue ArgumentError
97
- else
98
- fail ""
63
+ error = Blackstart.vet { ::Kernel.raise e_full }
64
+ fail "" unless String.equal? error.class
65
+ fail "" unless "FakeError: c\nd\ne" == error
99
66
  end
100
- begin
101
- Blackstart.add([], "invalid") {}
102
- rescue ArgumentError
103
- else
104
- fail ""
105
- end
106
-
107
- # When no collection is specified and the block does nothing, Blackstart.add
108
- # should return an empty array.
109
- fail "" unless [] == Blackstart.add {}
110
67
 
111
- # When a collection is specified and the block tries to add to the collection,
112
- # Blackstart.add should add corresponding procs or nil to the collection by
113
- # sending <<.
68
+ # If yielding to the block raises an error and that error raises an exception
69
+ # when queried, Blackstart.vet should raise that second exception.
114
70
  class ::Object
115
- sink = [42]
116
- prc1 = proc {}
117
- prc2 = proc {}
118
- retval = Blackstart.add sink do |adder|
119
- adder.call(&prc1)
120
- adder.call(&prc2)
121
- adder.call
71
+ e1 = StandardError.new
72
+ e2_class = Class.new StandardError
73
+ e1.instance_variable_set :@next_exception_class, e2_class
74
+ def e1.message
75
+ ::Kernel.raise @next_exception_class
122
76
  end
123
- fail "" unless sink.equal? retval
124
- fail "" unless [42, prc1, prc2, nil] == sink
125
- end
126
-
127
- # When the block sends call to the adder with arguments other than a block, it
128
- # should raise an exception and not add anything to the collection.
129
- class ::Object
130
- sink = []
131
77
  begin
132
- Blackstart.add(sink) { |adder| adder.call("invalid") {} }
133
- rescue ArgumentError
134
- else
135
- fail ""
136
- end
137
- begin
138
- Blackstart.add(sink) { |adder| adder.call "invalid" }
139
- rescue ArgumentError
78
+ Blackstart.vet { ::Kernel.raise e1 }
79
+ rescue e2_class
140
80
  else
141
81
  fail ""
142
82
  end
143
- fail "" unless 0 == sink.length
144
83
  end
145
84
 
146
- # When Blackstart.add's block sends call to the adder and it returns, nil
147
- # should be the object it returns.
85
+ # If yielding to the block raises a non-StandardError exception, Blackstart.vet
86
+ # should raise it.
148
87
  class ::Object
149
- retval_block = retval_no_block = true
150
- Blackstart.add do |adder|
151
- retval_block = adder.call {}
152
- retval_no_block = adder.call
153
- end
154
- fail "" unless nil.equal? retval_block
155
- fail "" unless nil.equal? retval_no_block
156
- end
157
-
158
- # In general, when there is a block that raises an exception, Blackstart.add
159
- # should not rescue it.
160
- class ::Object
161
- e = StandardError.new
88
+ non_standard_error = Class.new Exception
162
89
  begin
163
- Blackstart.add { ::Kernel.raise e }
164
- rescue
165
- fail "" unless e.equal? $!
90
+ Blackstart.vet { ::Kernel.raise non_standard_error }
91
+ rescue non_standard_error
166
92
  else
167
93
  fail ""
168
94
  end
169
95
  end
170
96
 
171
- # When there is a block that tries to add to the collection and the collection
172
- # responds to the << message by raising an exception, Blackstart.add should not
173
- # rescue it.
174
- begin
175
- Blackstart.add(nil) { |adder| adder.call {} }
176
- rescue NoMethodError
177
- else
178
- fail ""
179
- end
97
+ # If yielding to the block does not raise an exception, Blackstart.vet should
98
+ # return nil. The object returned shouldn't matter.
99
+ fail "" unless nil.equal? Blackstart.vet { nil }
100
+ fail "" unless nil.equal? Blackstart.vet { true }
180
101
 
181
- # When no collection is specified and the block tries to add to the collection,
182
- # Blackstart.add should append to a new array that gets returned.
183
- class ::Object
184
- prc = proc {}
185
- fail "" unless [prc] == Blackstart.add { |adder| adder.call(&prc) }
186
- end
102
+ # Test Blackstart::Scratchpad:
187
103
 
188
- # Test Blackstart::Context:
104
+ # Blackstart::Scratchpad.new should return an instance of
105
+ # Blackstart::Scratchpad, which implies that Blackstart::Scratchpad should be a
106
+ # class.
107
+ fail "" unless Blackstart::Scratchpad.equal? Blackstart::Scratchpad.new.class
189
108
 
190
- # Blackstart::Context.new should return an instance of Blackstart::Context,
191
- # which implies that Blackstart::Context should be a class.
192
- fail "" unless Blackstart::Context.equal? Blackstart::Context.new.class
193
-
194
- # Blackstart::Context.new should raise an exception if any non-block arguments
195
- # are sent.
196
- begin
197
- Blackstart::Context.new nil
198
- rescue ArgumentError
199
- else
200
- fail ""
201
- end
109
+ # A new scratchpad should have no instance variables.
110
+ fail "" unless 0 == Blackstart::Scratchpad.new.instance_variables.length
202
111
 
203
112
  # Test Blackstart.run:
204
113
 
@@ -206,7 +115,7 @@ end
206
115
  # return the appropriate object based on the behavior of the tests.
207
116
  class ::Object
208
117
  begin
209
- # Set $stdout to a test spy and set it back in the ensure clause.
118
+ # Set $stdout to a test spy and restore it in the ensure clause.
210
119
  original_stdout = $stdout
211
120
  spy_stdout = $stdout.clone
212
121
  spy_stdout.instance_variable_set :@_test_calls, stdout_calls = []
@@ -216,155 +125,235 @@ class ::Object
216
125
  end
217
126
  $stdout = spy_stdout
218
127
 
219
- # No tests.
220
- source = [].freeze
221
- fail "" unless true.equal? Blackstart.run(source, Object.new)
128
+ invalid_ostream = Object.new
129
+ def invalid_ostream.puts(*)
130
+ ::Kernel.raise ::NoMethodError
131
+ end
132
+
133
+ # When there are no tests, Blackstart.run should not notify the output
134
+ # stream of any failures.
135
+ source = []
136
+ fail "" unless true.equal? Blackstart.run(source, invalid_ostream)
222
137
  fail "" unless true.equal? Blackstart.run(source, spy_stdout)
223
138
  fail "" unless true.equal? Blackstart.run(source)
224
139
  fail "" unless 0 == stdout_calls.length
225
140
 
226
- # Tests that do not raise exceptions.
227
- source = [proc {}, proc { 42 }].freeze
228
- fail "" unless true.equal? Blackstart.run(source, Object.new)
141
+ # When there are tests but none raise exceptions, Blackstart.run should not
142
+ # notify the output stream of any failures. The objects returned by the
143
+ # test procs shouldn't affect Blackstart.run's behavior.
144
+ source = [proc {}, proc { 42 }]
145
+ fail "" unless true.equal? Blackstart.run(source, invalid_ostream)
229
146
  fail "" unless true.equal? Blackstart.run(source, spy_stdout)
230
147
  fail "" unless true.equal? Blackstart.run(source)
231
148
  fail "" unless 0 == stdout_calls.length
232
149
 
233
- # Define some example test objects.
234
- e_class = Class.new StandardError
150
+ # Define some test objects. Freeze them, the strings to which they convert,
151
+ # some exception details, and the strings to which the exception classes
152
+ # convert to check that they don't get modified.
153
+ e_class = Class.new StandardError do
154
+ def backtrace
155
+ ["line1".freeze, "line2".freeze].freeze
156
+ end
157
+ end
235
158
  def e_class.to_s
236
- "FakeError"
159
+ "FakeError".freeze
237
160
  end
238
- fail_prc1 = proc { ::Kernel.raise e_class, "fake message 1" }
161
+ fail_prc1 = proc { ::Kernel.raise e_class, "fake message 1".freeze }
239
162
  def fail_prc1.to_s
240
- "to_s 1"
163
+ "to_s 1".freeze
241
164
  end
165
+ fail_prc1.freeze
166
+ pass_prc = proc {}.freeze
242
167
  fail_prc2 = Object.new # Not a Proc instance, but converts to one.
243
168
  fail_prc2.instance_variable_set :@_test_e_class, e_class
244
169
  def fail_prc2.to_proc
245
170
  e_class = @_test_e_class
246
- ::Proc.new { ::Kernel.raise e_class, "fake message 2" }
171
+ ::Proc.new { ::Kernel.raise e_class, "fake message 2".freeze }.freeze
247
172
  end
248
173
  def fail_prc2.to_s
249
- "to_s 2"
174
+ "to_s 2".freeze
250
175
  end
251
- pass_prc = proc {}
252
-
253
- # A mix of tests that raise errors and tests that return.
254
- source = [fail_prc1, fail_prc2, pass_prc].freeze
255
-
256
- # Output stream that does not conform to the expected interface. (And in
257
- # general, the only exceptions Blackstart.run should rescue are
258
- # StandardError exceptions raised by tests.)
259
- begin
260
- Blackstart.run source, Object.new
261
- rescue NoMethodError
262
- else
263
- fail ""
176
+ fail_prc2.freeze
177
+ non_prc = Object.new # Does not convert to a proc.
178
+ def non_prc.to_proc
179
+ ::Kernel.raise ::NoMethodError
264
180
  end
265
- fail "" unless 0 == stdout_calls.length
181
+ def non_prc.to_s
182
+ "to_s 3".freeze
183
+ end
184
+ non_prc.freeze
185
+ bad_prc = Object.new # Pretends to convert to a proc but doesn't.
186
+ def bad_prc.to_proc
187
+ 0
188
+ end
189
+ def bad_prc.to_s
190
+ "to_s 4".freeze
191
+ end
192
+ bad_prc.freeze
266
193
 
267
- # Output stream that conforms to the expected interface.
194
+ # A mix of tests that raise errors and tests that return. Freeze the array
195
+ # to check that it's not being modified.
196
+ source = [fail_prc1, pass_prc, fail_prc2, non_prc, bad_prc].freeze
197
+
198
+ # An output stream that conforms to the expected interface and does not
199
+ # raise an exception should receive all failure information.
268
200
  spy_ostream = Object.new
269
201
  spy_ostream.instance_variable_set :@_test_calls, calls = []
270
202
  def spy_ostream.puts *args
271
203
  @_test_calls << args
272
- nil
204
+ # A real standard output stream would return nil here (and spy_stdout
205
+ # does that), but Blackstart.run's behavior should not be affected by the
206
+ # returned object. Returning this unusual object tests that.
207
+ ::Object.new
273
208
  end
209
+ # Freeze the output stream to check that it's not being modified.
210
+ spy_ostream.freeze
274
211
  fail "" unless false.equal? Blackstart.run(source, spy_ostream)
275
212
  fail "" unless 0 == stdout_calls.length
276
- fail "" unless 2 == calls.length
213
+ fail "" unless 4 == calls.length
277
214
  fail "" unless 5 == calls[0].length
215
+ fail "" unless calls[0].all? { |o| ::String.equal? o.class }
278
216
  fail "" unless "FAILED TEST:" == calls[0][0]
279
217
  fail "" unless "to_s 1" == calls[0][1]
280
218
  fail "" unless "...ERROR:" == calls[0][2]
281
- fail "" unless /\AFakeError: fake message 1$/ =~ calls[0][3]
219
+ fail "" unless "FakeError: fake message 1\nline1\nline2" == calls[0][3]
282
220
  fail "" unless "" == calls[0][4]
283
221
  fail "" unless 5 == calls[1].length
222
+ fail "" unless calls[1].all? { |o| ::String.equal? o.class }
284
223
  fail "" unless "FAILED TEST:" == calls[1][0]
285
224
  fail "" unless "to_s 2" == calls[1][1]
286
225
  fail "" unless "...ERROR:" == calls[1][2]
287
- fail "" unless /\AFakeError: fake message 2$/ =~ calls[1][3]
226
+ fail "" unless "FakeError: fake message 2\nline1\nline2" == calls[1][3]
288
227
  fail "" unless "" == calls[1][4]
289
- spy_ostream = calls = nil
290
-
291
- # Default output stream.
228
+ fail "" unless 5 == calls[2].length
229
+ fail "" unless calls[2].all? { |o| ::String.equal? o.class }
230
+ fail "" unless "FAILED TEST:" == calls[2][0]
231
+ fail "" unless "to_s 3" == calls[2][1]
232
+ fail "" unless "...ERROR:" == calls[2][2]
233
+ # (No need to check the error description.)
234
+ fail "" unless "" == calls[2][4]
235
+ fail "" unless 5 == calls[3].length
236
+ fail "" unless calls[3].all? { |o| ::String.equal? o.class }
237
+ fail "" unless "FAILED TEST:" == calls[3][0]
238
+ fail "" unless "to_s 4" == calls[3][1]
239
+ fail "" unless "...ERROR:" == calls[3][2]
240
+ # (No need to check the error description.)
241
+ fail "" unless "" == calls[3][4]
242
+
243
+ # The faked standard output stream.
292
244
  stdout_calls.clear
293
245
  fail "" unless false.equal? Blackstart.run(source)
294
- fail "" unless 2 == stdout_calls.length
246
+ fail "" unless 4 == stdout_calls.length
295
247
  fail "" unless 5 == stdout_calls[0].length
248
+ fail "" unless stdout_calls[0].all? { |o| ::String.equal? o.class }
296
249
  fail "" unless "FAILED TEST:" == stdout_calls[0][0]
297
250
  fail "" unless "to_s 1" == stdout_calls[0][1]
298
251
  fail "" unless "...ERROR:" == stdout_calls[0][2]
299
- fail "" unless /\AFakeError: fake message 1$/ =~ stdout_calls[0][3]
252
+ fail "" unless "FakeError: fake message 1\nline1\nline2" ==
253
+ stdout_calls[0][3]
300
254
  fail "" unless "" == stdout_calls[0][4]
301
255
  fail "" unless 5 == stdout_calls[1].length
256
+ fail "" unless stdout_calls[1].all? { |o| ::String.equal? o.class }
302
257
  fail "" unless "FAILED TEST:" == stdout_calls[1][0]
303
258
  fail "" unless "to_s 2" == stdout_calls[1][1]
304
259
  fail "" unless "...ERROR:" == stdout_calls[1][2]
305
- fail "" unless /\AFakeError: fake message 2$/ =~ stdout_calls[1][3]
260
+ fail "" unless "FakeError: fake message 2\nline1\nline2" ==
261
+ stdout_calls[1][3]
306
262
  fail "" unless "" == stdout_calls[1][4]
263
+ fail "" unless 5 == stdout_calls[2].length
264
+ fail "" unless stdout_calls[2].all? { |o| ::String.equal? o.class }
265
+ fail "" unless "FAILED TEST:" == stdout_calls[2][0]
266
+ fail "" unless "to_s 3" == stdout_calls[2][1]
267
+ fail "" unless "...ERROR:" == stdout_calls[2][2]
268
+ # (No need to check the error description.)
269
+ fail "" unless "" == stdout_calls[2][4]
270
+ fail "" unless 5 == stdout_calls[3].length
271
+ fail "" unless stdout_calls[3].all? { |o| ::String.equal? o.class }
272
+ fail "" unless "FAILED TEST:" == stdout_calls[3][0]
273
+ fail "" unless "to_s 4" == stdout_calls[3][1]
274
+ fail "" unless "...ERROR:" == stdout_calls[3][2]
275
+ # (No need to check the error description.)
276
+ fail "" unless "" == stdout_calls[3][4]
307
277
  stdout_calls.clear
308
278
 
309
- # Test that raises a non-StandardError exception. (And in general, the only
310
- # exceptions Blackstart.run should rescue are StandardError exceptions
311
- # raised by tests.)
312
- non_standard_error = Class.new Exception
313
- source = [proc { ::Kernel.raise non_standard_error }]
279
+ # An output stream that raises an exception when notified of a failure
280
+ # should cause Blackstart.run to raise that exception and stop processing
281
+ # tests. (In general, the only exceptions Blackstart.run should rescue are
282
+ # StandardError exceptions raised by tests.)
283
+ second_test_run = false
284
+ source = [proc { ::Kernel.raise "" }, proc { second_test_run = true }]
314
285
  begin
315
- Blackstart.run source, spy_stdout
316
- rescue non_standard_error
286
+ Blackstart.run source, invalid_ostream
287
+ rescue NoMethodError
317
288
  else
318
289
  fail ""
319
290
  end
291
+ fail "" if second_test_run
320
292
  fail "" unless 0 == stdout_calls.length
321
293
 
322
- # Test sequence that does not conform to the interface. (And in general,
323
- # the only exceptions Blackstart.run should rescue are StandardError
324
- # exceptions raised by tests.)
294
+ # When a test raises a non-StandardError exception, Blackstart.run should
295
+ # raise that exception and stop processing tests. (In general, the only
296
+ # exceptions Blackstart.run should rescue are StandardError exceptions
297
+ # raised by tests.)
298
+ second_test_run = false
299
+ non_standard_error = Class.new Exception
300
+ source = [proc { ::Kernel.raise non_standard_error },
301
+ proc { second_test_run = true }]
325
302
  begin
326
- Blackstart.run nil, spy_stdout
327
- rescue NoMethodError
303
+ Blackstart.run source, spy_stdout
304
+ rescue non_standard_error
328
305
  else
329
306
  fail ""
330
307
  end
308
+ fail "" if second_test_run
331
309
  fail "" unless 0 == stdout_calls.length
332
310
 
333
- # Test sequences containing objects that do not convert to procs (and do
334
- # not pretend to) and objects that pretend to convert to procs but do not.
335
- # (And in general, the only exceptions Blackstart.run should rescue are
311
+ # When the test sequence does not conform to the interface and raises an
312
+ # exception during iteration, Blackstart.run should raise that exception.
313
+ # (In general, the only exceptions Blackstart.run should rescue are
336
314
  # StandardError exceptions raised by tests.)
337
- source = [nil]
338
- begin
339
- Blackstart.run source, spy_stdout
340
- rescue ArgumentError
341
- else
342
- fail ""
315
+ source = Object.new
316
+ def source.each(*)
317
+ ::Kernel.raise ::NoMethodError
343
318
  end
344
- source = [0]
345
319
  begin
346
320
  Blackstart.run source, spy_stdout
347
- rescue TypeError
321
+ rescue NoMethodError
348
322
  else
349
323
  fail ""
350
324
  end
351
- bad_to_proc = Object.new
352
- def bad_to_proc.to_proc
353
- nil
325
+ fail "" unless 0 == stdout_calls.length
326
+
327
+ # When a test fails and converting the test object to a string raises an
328
+ # exception, Blackstart.run should raise that exception and stop processing
329
+ # tests. (In general, the only exceptions Blackstart.run should rescue are
330
+ # StandardError exceptions raised by tests.)
331
+ second_test_run = false
332
+ no_desc = proc { ::Kernel.raise "" }
333
+ def no_desc.to_s
334
+ ::Kernel.raise ::NoMethodError
354
335
  end
355
- source = [bad_to_proc]
336
+ source = [no_desc, proc { second_test_run = true }]
356
337
  begin
357
338
  Blackstart.run source, spy_stdout
358
- rescue TypeError
339
+ rescue NoMethodError
359
340
  else
360
341
  fail ""
361
342
  end
343
+ fail "" if second_test_run
362
344
  fail "" unless 0 == stdout_calls.length
363
345
  ensure
364
346
  $stdout = original_stdout
365
347
  end
366
348
  end
367
349
 
350
+ # Blackstart.run should run tests in order.
351
+ class ::Object
352
+ data = []
353
+ Blackstart.run [proc { data << 1 }, proc { data << 2 }], nil
354
+ fail "" unless [1, 2] == data
355
+ end
356
+
368
357
  # Blackstart.run should call each test proc with no arguments.
369
358
  class ::Object
370
359
  args = nil
@@ -375,14 +364,55 @@ class ::Object
375
364
  fail "" unless [] == args
376
365
  end
377
366
 
378
- # Blackstart.run should call each test proc in the context of a unique instance
379
- # of Blackstart::Context.
367
+ # Blackstart.run should work with any source that responds to each. The object
368
+ # returned in response to the each message should not affect Blackstart.run's
369
+ # behavior.
370
+ class ::Object
371
+ source = Object.new
372
+ def source.each
373
+ ran = false
374
+ yield proc { ran = true }
375
+ @ran = ran
376
+ self
377
+ end
378
+ fail "" unless true.equal? Blackstart.run(source, nil)
379
+ fail "" unless source.instance_variable_get :@ran
380
+
381
+ source = Object.new
382
+ def source.each
383
+ yield proc {}
384
+ nil
385
+ end
386
+ fail "" unless true.equal? Blackstart.run(source, nil)
387
+ end
388
+
389
+ # Blackstart.run should send exactly one each message to the source.
390
+ class ::Object
391
+ source = Object.new
392
+ source.instance_variable_set :@count, 0
393
+ def source.each
394
+ @count += 1
395
+ self
396
+ end
397
+ Blackstart.run source, nil
398
+ fail "" unless 1 == source.instance_variable_get(:@count)
399
+ end
400
+
401
+ # When Blackstart.run calls a test proc, the self object should be a unique
402
+ # instance of Blackstart::Scratchpad.
380
403
  class ::Object
381
- context1 = context2 = nil
382
- Blackstart.run [proc { context1 = self }, proc { context2 = self }], nil
383
- fail "" unless Blackstart::Context.equal? context1.class
384
- fail "" unless Blackstart::Context.equal? context2.class
385
- fail "" if context1.equal? context2
404
+ sp1 = sp2 = nil
405
+ Blackstart.run [proc { sp1 = self }, proc { sp2 = self }], nil
406
+ fail "" unless Blackstart::Scratchpad.equal? sp1.class
407
+ fail "" unless Blackstart::Scratchpad.equal? sp2.class
408
+ fail "" if sp1.equal? sp2
409
+ end
410
+
411
+ # When Blackstart.run runs a test, the scratchpad should not be frozen.
412
+ class ::Object
413
+ frozen = true
414
+ Blackstart.run [proc { frozen = frozen? }], nil
415
+ fail "" if frozen
386
416
  end
387
417
 
388
418
  # Blackstart.run should raise an exception if too few or too many non-block
@@ -393,11 +423,34 @@ rescue ArgumentError
393
423
  else
394
424
  fail ""
395
425
  end
396
- begin
397
- Blackstart.run [], nil, "invalid"
398
- rescue ArgumentError
399
- else
400
- fail ""
426
+ class ::Object
427
+ source = []
428
+ begin
429
+ Blackstart.run source, nil, nil
430
+ rescue ArgumentError
431
+ else
432
+ fail ""
433
+ end
434
+ end
435
+
436
+ # The following section modifies the state of the library, so it must be run
437
+ # after all the ordinary tests.
438
+
439
+ # Blackstart.run should run each test by sending instance_exec to a scratchpad.
440
+ class Blackstart::Scratchpad
441
+ def foo
442
+ 2
443
+ end
444
+
445
+ def instance_exec(*)
446
+ @ivar = 1
447
+ super
448
+ end
449
+ end
450
+ class ::Object
451
+ result = nil
452
+ Blackstart.run [proc { result = [@ivar, foo()] }], nil
453
+ fail "" unless [1, 2] == result
401
454
  end
402
455
 
403
456
  # If this message doesn't get written, there was a problem.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blackstart
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 5
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aaron Beckerman
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2024-03-19 00:00:00 -07:00
18
+ date: 2024-12-21 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21