erudite 0.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.
- checksums.yaml +15 -0
- data/CHANGELOG.md +1 -0
- data/CONTRIBUTING.md +7 -0
- data/LICENSE.md +18 -0
- data/README.md +53 -0
- data/lib/erudite.rb +9 -0
- data/lib/erudite/example.rb +79 -0
- data/lib/erudite/outcome.rb +19 -0
- data/lib/erudite/parser.rb +54 -0
- data/spec/erudite/example_spec.rb +352 -0
- data/spec/erudite/outcome_spec.rb +53 -0
- data/spec/erudite/parser_spec.rb +104 -0
- data/spec/erudite_spec.rb +7 -0
- data/spec/spec_helper.rb +3 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MmMyNDJiNGZhYjc3YmY5NDJmNjA1ZWY2OWZmNDRkMDdiYjAwMzA0MQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjA3YjdkZWY3OTVjMDM2OTI2NTUzZWU2YzRhNTViMjI5NmE4NjkxOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjllZjIyNjdkMWE2YjQwNGJmNmUxZTQyYTA5ODFhNmI5OGIxYTllYTU4NDNh
|
10
|
+
OWM5M2Y2N2ZhMWM0YzIxNzI4NzdjOTI3MTQ0YTFlNjdlNzU2ZjhhMjNmZDE4
|
11
|
+
ZDhhMWU1NTc5NzUwY2Q0ZWIxYzVmNDEzZjQzOGE1OWMxNzY4OTU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MThjNzM2YTllMTY2NzBkNGY4ZjYwZTRmODljMGE1YTkzOGNmYmMzYzQzMDg4
|
14
|
+
N2Y1YzIwY2I4Y2JiMmJmNGVmMTBiMWQwMTY4YjIzZDcxMzk5NmJmZWQ4ZDFh
|
15
|
+
YjhlMGMyOTBmMjVkOTYyYzZkZTI3YTY1NWU1MDI4YzRlMGY2NzY=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Changelog
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
1. **Fork** the repository.
|
4
|
+
2. Create a **branch** for your feature (`git checkout -b feature`).
|
5
|
+
3. **Commit** your changes (`git commit -a -m 'Feature'`).
|
6
|
+
4. **Push** to your branch (`git push origin feature`).
|
7
|
+
5. Create a **pull request**.
|
data/LICENSE.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2014 Taylor Fausak <taylor@fausak.me>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# [Erudite][1]
|
2
|
+
|
3
|
+
Executable documentation.
|
4
|
+
|
5
|
+
``` rb
|
6
|
+
examples = Erudite::Parser.parse(<<-'RUBY')
|
7
|
+
>> 1 + 2
|
8
|
+
=> 3
|
9
|
+
|
10
|
+
>> Object.new
|
11
|
+
=> #<Object:0x...>
|
12
|
+
|
13
|
+
>> gets
|
14
|
+
=> nil
|
15
|
+
|
16
|
+
>> puts 'hello world'
|
17
|
+
hello world
|
18
|
+
=> nil
|
19
|
+
|
20
|
+
>> warn 'oh noes'
|
21
|
+
oh noes
|
22
|
+
=> nil
|
23
|
+
|
24
|
+
>> fail 'catastrophe!'
|
25
|
+
RuntimeError: catastrophe!
|
26
|
+
|
27
|
+
>> puts 'chunky
|
28
|
+
.. bacon'
|
29
|
+
chunky
|
30
|
+
bacon
|
31
|
+
=> nil
|
32
|
+
|
33
|
+
>> def double(n)
|
34
|
+
.. 2 * n
|
35
|
+
.. end
|
36
|
+
>> double(3)
|
37
|
+
=> 6
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
examples.each do |example|
|
41
|
+
if example.pass?
|
42
|
+
puts 'PASS'
|
43
|
+
else
|
44
|
+
puts 'FAIL'
|
45
|
+
puts " expected : #{example.expected.result.inspect}"
|
46
|
+
puts " : #{example.expected.output.inspect}"
|
47
|
+
puts " actual : #{example.actual.result.inspect}"
|
48
|
+
puts " : #{example.actual.output.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
[1]: https://github.com/tfausak/erudite
|
data/lib/erudite.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Erudite
|
6
|
+
# Code to be run and compared against its expected outcome.
|
7
|
+
class Example
|
8
|
+
attr_reader :source
|
9
|
+
attr_reader :expected
|
10
|
+
attr_writer :binding
|
11
|
+
|
12
|
+
def initialize(source, result = nil, output = nil)
|
13
|
+
@source = source
|
14
|
+
@expected = Outcome.new(result, output)
|
15
|
+
end
|
16
|
+
|
17
|
+
def binding
|
18
|
+
@binding ||= TOPLEVEL_BINDING.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!
|
22
|
+
binding.eval(source, __FILE__, __LINE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
[run!, nil]
|
27
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
28
|
+
[nil, exception]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.without_stdio
|
32
|
+
stdin, stdout, stderr = $stdin, $stdout, $stderr
|
33
|
+
$stdin = $stdout = $stderr = io = StringIO.new
|
34
|
+
[yield, io]
|
35
|
+
ensure
|
36
|
+
$stdin, $stdout, $stderr = stdin, stdout, stderr
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.format_exception(exception)
|
40
|
+
"#{exception.class.name}: #{exception.message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def actual
|
44
|
+
return @actual if defined?(@actual)
|
45
|
+
|
46
|
+
result, io = self.class.without_stdio do
|
47
|
+
result, exception = run
|
48
|
+
warn(self.class.format_exception(exception)) if exception
|
49
|
+
result.inspect
|
50
|
+
end
|
51
|
+
|
52
|
+
@actual = Outcome.new(result, io.string)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.pattern(string)
|
56
|
+
Regexp.new(Regexp.escape(string).gsub('\.\.\.', '.*?'))
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_result?
|
60
|
+
return true unless expected.result
|
61
|
+
self.class.pattern(expected.result).match(actual.result)
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid_output?
|
65
|
+
return true unless expected.output
|
66
|
+
self.class.pattern(expected.output).match(actual.output)
|
67
|
+
end
|
68
|
+
|
69
|
+
def pass?
|
70
|
+
actual
|
71
|
+
valid_result? && valid_output?
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(other)
|
75
|
+
source == other.source &&
|
76
|
+
expected == other.expected
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Erudite
|
4
|
+
# Information about an expected or actual outcome.
|
5
|
+
class Outcome
|
6
|
+
attr_reader :result
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
def initialize(result, output)
|
10
|
+
@result = result
|
11
|
+
@output = output
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
result == other.result &&
|
16
|
+
output == other.output
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Erudite
|
4
|
+
# Parses IRB output into examples.
|
5
|
+
class Parser
|
6
|
+
def self.parse(string)
|
7
|
+
group(string).map { |lines| exemplify(lines) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.group(string)
|
11
|
+
buffer = []
|
12
|
+
|
13
|
+
groups = string.each_line.each_with_object([]) do |line, array|
|
14
|
+
if line.start_with?('>> ') && !buffer.empty?
|
15
|
+
array.push(buffer)
|
16
|
+
buffer = []
|
17
|
+
end
|
18
|
+
|
19
|
+
buffer.push(line)
|
20
|
+
end
|
21
|
+
|
22
|
+
buffer.empty? ? groups : groups.push(buffer)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.exemplify(lines)
|
26
|
+
source = extract_source(lines)
|
27
|
+
result = extract_result(lines)
|
28
|
+
output = extract_output(lines)
|
29
|
+
|
30
|
+
Example.new(source, result, output)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.extract_source(lines)
|
34
|
+
source = lines
|
35
|
+
.select { |line| line.start_with?('>> ', '.. ') }
|
36
|
+
.map { |line| line[3..-1].chomp }
|
37
|
+
source.join("\n") unless source.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.extract_result(lines)
|
41
|
+
result = lines
|
42
|
+
.select { |line| line.start_with?('=> ') }
|
43
|
+
.map { |line| line[3..-1].chomp }
|
44
|
+
result.join("\n") unless result.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.extract_output(lines)
|
48
|
+
output = lines
|
49
|
+
.reject { |line| line.start_with?('>> ', '.. ', '=> ') }
|
50
|
+
.map { |line| line.chomp }
|
51
|
+
output.join("\n") unless output.empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,352 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Erudite::Example do
|
6
|
+
it 'requires some source' do
|
7
|
+
expect { described_class.new }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can be initialized' do
|
11
|
+
expect(described_class.new(nil)).to be_a(described_class)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'sets the source' do
|
15
|
+
source = double
|
16
|
+
example = described_class.new(source)
|
17
|
+
expect(example.source).to eql(source)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'uses the default expected outcome' do
|
21
|
+
example = described_class.new(nil)
|
22
|
+
expect(example.expected).to be_an(Erudite::Outcome)
|
23
|
+
expect(example.expected.result).to be(nil)
|
24
|
+
expect(example.expected.output).to be(nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sets the expected result' do
|
28
|
+
result = double
|
29
|
+
example = described_class.new(nil, result)
|
30
|
+
expect(example.expected.result).to eql(result)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sets the expected output' do
|
34
|
+
output = double
|
35
|
+
example = described_class.new(nil, nil, output)
|
36
|
+
expect(example.expected.output).to eql(output)
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#binding' do
|
40
|
+
it 'uses the default binding' do
|
41
|
+
example = described_class.new(nil)
|
42
|
+
expect(example.binding).to be_a(Binding)
|
43
|
+
expect(example.binding.eval('self')).to be(TOPLEVEL_BINDING.eval('self'))
|
44
|
+
expect(example.binding).to_not be(TOPLEVEL_BINDING)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'memoizes the binding' do
|
48
|
+
example = described_class.new(nil)
|
49
|
+
binding = example.binding
|
50
|
+
expect(example.binding).to be(binding)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sets the binding' do
|
54
|
+
example = described_class.new(nil)
|
55
|
+
binding = TOPLEVEL_BINDING.dup
|
56
|
+
example.binding = binding
|
57
|
+
expect(example.binding).to be(binding)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#run!' do
|
62
|
+
it 'evaluates the source' do
|
63
|
+
source = ':something'
|
64
|
+
example = described_class.new(source)
|
65
|
+
expect(example.run!).to eql(TOPLEVEL_BINDING.eval(source))
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'evaluates the source in the binding' do
|
69
|
+
source = 'variable'
|
70
|
+
example = described_class.new(source)
|
71
|
+
example.binding.eval('variable = :something')
|
72
|
+
expect(example.run!).to eql(example.binding.eval(source))
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets the file name' do
|
76
|
+
example = described_class.new('fail')
|
77
|
+
begin
|
78
|
+
example.run!
|
79
|
+
rescue => error
|
80
|
+
expect(error.backtrace.first).to_not start_with('<main>:')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'sets the line number' do
|
85
|
+
example = described_class.new('fail')
|
86
|
+
begin
|
87
|
+
example.run!
|
88
|
+
rescue => error
|
89
|
+
expect(error.backtrace.first).to_not end_with(":1:in `<main>'")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#run' do
|
95
|
+
it 'returns the result' do
|
96
|
+
source = ':something'
|
97
|
+
example = described_class.new(source)
|
98
|
+
expect(example.run.first).to eql(TOPLEVEL_BINDING.eval(source))
|
99
|
+
expect(example.run.last).to be(nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns the exception' do
|
103
|
+
source = 'fail'
|
104
|
+
example = described_class.new(source)
|
105
|
+
expect(example.run.first).to be(nil)
|
106
|
+
expect(example.run.last).to be_a(StandardError)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '.without_stdio' do
|
111
|
+
it 'requires a block' do
|
112
|
+
expect { described_class.without_stdio }.to raise_error(LocalJumpError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'calls the block' do
|
116
|
+
result = double
|
117
|
+
expect(described_class.without_stdio { result }.first).to eql(result)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'redirects STDIN' do
|
121
|
+
stdin = $stdin
|
122
|
+
expect(described_class.without_stdio { $stdin }).to_not eq(stdin)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'redirects STDOUT' do
|
126
|
+
stdout = $stdout
|
127
|
+
expect(described_class.without_stdio { $stdout }).to_not eq(stdout)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'redirects STDERR' do
|
131
|
+
stderr = $stderr
|
132
|
+
expect(described_class.without_stdio { $stderr }).to_not eq(stderr)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'reconnects STDIN' do
|
136
|
+
stdin = $stdin
|
137
|
+
begin
|
138
|
+
described_class.without_stdio { fail }
|
139
|
+
rescue
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
expect($stdin).to eq(stdin)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'reconnects STDOUT' do
|
146
|
+
stdout = $stdout
|
147
|
+
begin
|
148
|
+
described_class.without_stdio { fail }
|
149
|
+
rescue
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
expect($stdout).to eq(stdout)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'reconnects STDERR' do
|
156
|
+
stderr = $stderr
|
157
|
+
begin
|
158
|
+
described_class.without_stdio { fail }
|
159
|
+
rescue
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
expect($stderr).to eq(stderr)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'returns the redirected IO' do
|
166
|
+
expect(described_class.without_stdio {}.last).to be_a(StringIO)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'handles STDIN' do
|
170
|
+
result, _ = described_class.without_stdio { gets }
|
171
|
+
expect(result).to be(nil)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'handles STDOUT' do
|
175
|
+
_, io = described_class.without_stdio { puts 'something' }
|
176
|
+
expect(io.string).to eql("something\n")
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'handles STDERR' do
|
180
|
+
_, io = described_class.without_stdio { warn 'something' }
|
181
|
+
expect(io.string).to eql("something\n")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '.format_exception' do
|
186
|
+
it 'formats the exception' do
|
187
|
+
exception = StandardError.new('something')
|
188
|
+
expect(described_class.format_exception(exception))
|
189
|
+
.to eql("#{exception.class.name}: #{exception.message}")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#actual' do
|
194
|
+
it 'sets the result' do
|
195
|
+
example = described_class.new('"something"')
|
196
|
+
expect(example.actual.result).to eql('"something"')
|
197
|
+
expect(example.actual.output).to eql('')
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'handles STDIN' do
|
201
|
+
example = described_class.new('gets')
|
202
|
+
expect(example.actual.result).to eql('nil')
|
203
|
+
expect(example.actual.output).to eql('')
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'handles STDOUT' do
|
207
|
+
example = described_class.new('puts "something"')
|
208
|
+
expect(example.actual.result).to eql('nil')
|
209
|
+
expect(example.actual.output).to eql("something\n")
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'handles STDERR' do
|
213
|
+
example = described_class.new('warn "something"')
|
214
|
+
expect(example.actual.result).to eql('nil')
|
215
|
+
expect(example.actual.output).to eql("something\n")
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'handles exceptions' do
|
219
|
+
example = described_class.new('fail StandardError, "something"')
|
220
|
+
expect(example.actual.result).to eql('nil')
|
221
|
+
expect(example.actual.output).to eql("StandardError: something\n")
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'memoizes the result' do
|
225
|
+
example = described_class.new('')
|
226
|
+
actual = example.actual
|
227
|
+
expect(example.actual).to be(actual)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '.pattern' do
|
232
|
+
it 'returns a Regexp' do
|
233
|
+
pattern = described_class.pattern('something')
|
234
|
+
expect(pattern).to be_a(Regexp)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'escapes meta characters' do
|
238
|
+
pattern = described_class.pattern('some.thing')
|
239
|
+
expect(pattern).to eql(/some\.thing/)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'replaces "..."' do
|
243
|
+
pattern = described_class.pattern('some...thing')
|
244
|
+
expect(pattern).to eql(/some.*?thing/)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe '#valid_result?' do
|
249
|
+
it 'returns true without an expected result' do
|
250
|
+
example = described_class.new(nil, nil)
|
251
|
+
expect(example).to be_valid_result
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'returns true when the results match' do
|
255
|
+
example = described_class.new('"something"', '"something"')
|
256
|
+
expect(example).to be_valid_result
|
257
|
+
end
|
258
|
+
|
259
|
+
it "returns false when the results don't match" do
|
260
|
+
example = described_class.new('"something"', '"something else"')
|
261
|
+
expect(example).to_not be_valid_result
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'allows wildcards' do
|
265
|
+
example = described_class.new('Object.new', '#<Object:0x...>')
|
266
|
+
expect(example).to be_valid_result
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe '#valid_output?' do
|
271
|
+
it 'returns true without expected output' do
|
272
|
+
example = described_class.new(nil, nil)
|
273
|
+
expect(example).to be_valid_output
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'returns true when the output matches' do
|
277
|
+
example = described_class.new('puts "something"', nil, 'something')
|
278
|
+
expect(example).to be_valid_output
|
279
|
+
end
|
280
|
+
|
281
|
+
it "returns false when the output doesn't match" do
|
282
|
+
example = described_class.new('puts "something"', nil, 'something else')
|
283
|
+
expect(example).to_not be_valid_output
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'allows wildcards' do
|
287
|
+
example = described_class.new('puts Object.new', nil, '#<Object:0x...>')
|
288
|
+
expect(example).to be_valid_output
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe '#pass?' do
|
293
|
+
it 'realizes the actual outcome' do
|
294
|
+
example = described_class.new('x = 1')
|
295
|
+
example.pass?
|
296
|
+
expect(example.binding.eval('x')).to eql(1)
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'returns true if both the result and output match' do
|
300
|
+
example = described_class
|
301
|
+
.new('p "something"', '"something"', '"something"')
|
302
|
+
expect(example).to be_pass
|
303
|
+
end
|
304
|
+
|
305
|
+
it "returns false if the result doesn't match" do
|
306
|
+
example = described_class
|
307
|
+
.new('p "something"', '"something else"', '"something"')
|
308
|
+
expect(example).to_not be_pass
|
309
|
+
end
|
310
|
+
|
311
|
+
it "returns false if the output doesn't match" do
|
312
|
+
example = described_class
|
313
|
+
.new('p "something"', '"something"', '"something else"')
|
314
|
+
expect(example).to_not be_pass
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe '#==' do
|
319
|
+
it 'returns true when they have the same source, result, and output' do
|
320
|
+
source = 'x'
|
321
|
+
result = 'y'
|
322
|
+
output = 'z'
|
323
|
+
a = described_class.new(source, result, output)
|
324
|
+
b = described_class.new(source, result, output)
|
325
|
+
expect(a).to eq(b)
|
326
|
+
end
|
327
|
+
|
328
|
+
it "returns false when they don't have the same source" do
|
329
|
+
result = 'y'
|
330
|
+
output = 'z'
|
331
|
+
a = described_class.new('a', result, output)
|
332
|
+
b = described_class.new('b', result, output)
|
333
|
+
expect(a).to_not eq(b)
|
334
|
+
end
|
335
|
+
|
336
|
+
it "returns false when they don't have the same result" do
|
337
|
+
source = 'x'
|
338
|
+
output = 'z'
|
339
|
+
a = described_class.new(source, 'a', output)
|
340
|
+
b = described_class.new(source, 'b', output)
|
341
|
+
expect(a).to_not eq(b)
|
342
|
+
end
|
343
|
+
|
344
|
+
it "returns false when they don't have the same output" do
|
345
|
+
source = 'x'
|
346
|
+
result = 'y'
|
347
|
+
a = described_class.new(source, result, 'a')
|
348
|
+
b = described_class.new(source, result, 'b')
|
349
|
+
expect(a).to_not eq(b)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Erudite::Outcome do
|
6
|
+
it 'requires a result' do
|
7
|
+
expect { described_class.new }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'requires some output' do
|
11
|
+
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be initialized' do
|
15
|
+
expect(described_class.new(nil, nil)).to be_a(described_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets the result' do
|
19
|
+
result = double
|
20
|
+
outcome = described_class.new(result, nil)
|
21
|
+
expect(outcome.result).to eql(result)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets the output' do
|
25
|
+
output = double
|
26
|
+
outcome = described_class.new(nil, output)
|
27
|
+
expect(outcome.output).to eql(output)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#==' do
|
31
|
+
it 'returns true if they have the same result and output' do
|
32
|
+
result = 'some'
|
33
|
+
output = 'thing'
|
34
|
+
a = described_class.new(result, output)
|
35
|
+
b = described_class.new(result, output)
|
36
|
+
expect(a).to eq(b)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns false if they don't have the same result" do
|
40
|
+
output = 'something'
|
41
|
+
a = described_class.new('a', output)
|
42
|
+
b = described_class.new('b', output)
|
43
|
+
expect(a).to_not eq(b)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns false if they don't have the same output" do
|
47
|
+
result = 'something'
|
48
|
+
a = described_class.new(result, 'a')
|
49
|
+
b = described_class.new(result, 'b')
|
50
|
+
expect(a).to_not eq(b)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
# Monkey patch String to add ability to strip leading whitespace.
|
6
|
+
class String
|
7
|
+
def dedent
|
8
|
+
gsub(/^#{self[/\A\s*/]}/, '')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Erudite::Parser do
|
13
|
+
describe '.parse' do
|
14
|
+
it 'parses an example without output or a result' do
|
15
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
16
|
+
>> :something
|
17
|
+
RUBY
|
18
|
+
expect(examples).to eq([
|
19
|
+
Erudite::Example.new(':something')])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'parses an example with a result' do
|
23
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
24
|
+
>> :something
|
25
|
+
=> :something
|
26
|
+
RUBY
|
27
|
+
expect(examples).to eq([
|
28
|
+
Erudite::Example.new(':something', ':something')])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'parses an example with output' do
|
32
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
33
|
+
>> puts :something
|
34
|
+
something
|
35
|
+
RUBY
|
36
|
+
expect(examples).to eq([
|
37
|
+
Erudite::Example.new('puts :something', nil, 'something')])
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'handles STDIN' do
|
41
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
42
|
+
>> gets
|
43
|
+
=> nil
|
44
|
+
RUBY
|
45
|
+
expect(examples).to eq([
|
46
|
+
Erudite::Example.new('gets', 'nil')])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'handles STDOUT' do
|
50
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
51
|
+
>> puts :something
|
52
|
+
something
|
53
|
+
=> nil
|
54
|
+
RUBY
|
55
|
+
expect(examples).to eq([
|
56
|
+
Erudite::Example.new('puts :something', 'nil', 'something')])
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'handles STDERR' do
|
60
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
61
|
+
>> warn :something
|
62
|
+
something
|
63
|
+
=> nil
|
64
|
+
RUBY
|
65
|
+
expect(examples).to eq([
|
66
|
+
Erudite::Example.new('warn :something', 'nil', 'something')])
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'handles exceptions' do
|
70
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
71
|
+
>> fail 'something'
|
72
|
+
RuntimeError: something
|
73
|
+
RUBY
|
74
|
+
expect(examples).to eq([
|
75
|
+
Erudite::Example.new(
|
76
|
+
"fail 'something'", nil, 'RuntimeError: something')])
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'handles multi-line source and output' do
|
80
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
81
|
+
>> puts 'some
|
82
|
+
.. thing'
|
83
|
+
some
|
84
|
+
thing
|
85
|
+
=> nil
|
86
|
+
RUBY
|
87
|
+
expect(examples).to eq([
|
88
|
+
Erudite::Example.new("puts 'some\nthing'", 'nil', "some\nthing")])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'handles multiple examples' do
|
92
|
+
examples = described_class.parse(<<-'RUBY'.dedent)
|
93
|
+
>> def some
|
94
|
+
.. :thing
|
95
|
+
.. end
|
96
|
+
>> some
|
97
|
+
=> :thing
|
98
|
+
RUBY
|
99
|
+
expect(examples).to eq([
|
100
|
+
Erudite::Example.new("def some\n :thing\nend"),
|
101
|
+
Erudite::Example.new('some', ':thing')])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: erudite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Taylor Fausak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 10.3.2
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 10.3.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.0.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.25.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.25.0
|
55
|
+
description: Executable documentation.
|
56
|
+
email: taylor@fausak.me
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- CHANGELOG.md
|
62
|
+
- CONTRIBUTING.md
|
63
|
+
- LICENSE.md
|
64
|
+
- README.md
|
65
|
+
- lib/erudite.rb
|
66
|
+
- lib/erudite/example.rb
|
67
|
+
- lib/erudite/outcome.rb
|
68
|
+
- lib/erudite/parser.rb
|
69
|
+
- spec/erudite/example_spec.rb
|
70
|
+
- spec/erudite/outcome_spec.rb
|
71
|
+
- spec/erudite/parser_spec.rb
|
72
|
+
- spec/erudite_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
homepage: https://github.com/tfausak/erudite
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.9.3
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.1
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Executable documentation.
|
98
|
+
test_files:
|
99
|
+
- spec/erudite/example_spec.rb
|
100
|
+
- spec/erudite/outcome_spec.rb
|
101
|
+
- spec/erudite/parser_spec.rb
|
102
|
+
- spec/erudite_spec.rb
|
103
|
+
- spec/spec_helper.rb
|