r_spec 0.3.1 → 1.0.0.beta3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9b22b4685a1ce6a25e848c1d954859dd5bbe5edf
4
- data.tar.gz: c1ff7f58da140a017a2fba52ad7139aada7f2355
2
+ SHA256:
3
+ metadata.gz: 7baead9bb63726ef4e29f1ae2df0ff2584177377d1ddda34377d0fb37503ec64
4
+ data.tar.gz: 52ebf75f1e76d238ffc7592fd624e6af5bc2eed3423d5742accfcb3222555560
5
5
  SHA512:
6
- metadata.gz: 5bad34ba991c5253842694094d9ae7bd3608058d272357a756a562470381a853164f020b865aadd53cb04dec78ba00c248c84f1e0a8af68fb69ceae957bbe41b
7
- data.tar.gz: a79b3e7b6e5e216746c4f4bd7ac3f7faa601585f728d95afff65924914c12a603e9e2ef4af18f25002592db79ed09b34be7d71b734ab60d8217fe615b99f870d
6
+ metadata.gz: 985f437d4b2a9ffa63a1a14f9d588ba44bea017e5374ec50ba718c41497a0e91cc41ea7705928c7d7b38b3bbc11f6b6f114929d2e81ba1191be02b248b7e43bf
7
+ data.tar.gz: c525c43c2259150b9122bbf6a84bf61cbcd6323109992a7ce9ddd2248143788cbc6c15bf4b4f231648aadba29a8e1fed80dc068d9e2462a2b903299ec9c1c174
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Cyril Kato
3
+ Copyright (c) 2015-2021 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,136 +1,298 @@
1
- # R Spec
1
+ # RSpec clone
2
2
 
3
- [![Build Status](https://travis-ci.org/cyril/r_spec.svg?branch=master)][travis]
4
- [![Code Climate](https://codeclimate.com/github/cyril/r_spec/badges/gpa.svg)][codeclimate]
5
- [![Dependency Status](https://gemnasium.com/cyril/r_spec.svg)][gemnasium]
6
- [![Gem Version](https://badge.fury.io/rb/r_spec.svg)][gem]
7
- [![Inline docs](http://inch-ci.org/github/cyril/r_spec.svg?branch=master)][inchpages]
8
- [![Documentation](http://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ A minimalist [RSpec](https://github.com/rspec/rspec) clone with all the essentials.
9
4
 
10
- > A small [Rspec](https://github.com/rspec/rspec) clone based on [Fix specing framework](https://github.com/fixrb/fix).
5
+ ![What did you expect?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-expect.jpg)
11
6
 
12
- ***
7
+ ## Status
13
8
 
14
- :warning: Important:
9
+ [![Gem Version](https://badge.fury.io/rb/r_spec.svg)](https://badge.fury.io/rb/r_spec)
10
+ [![Build Status](https://travis-ci.org/cyril/r_spec.rb.svg?branch=main)](https://travis-ci.org/cyril/r_spec.rb)
11
+ [![Inline Docs](https://inch-ci.org/github/cyril/r_spec.rb.svg)](https://inch-ci.org/github/cyril/r_spec.rb)
15
12
 
16
- To avoid confusion in the community, please note that the gem of this project is **not [rspec](https://rubygems.org/gems/rspec)**, it is **[r_spec](https://rubygems.org/gems/r_spec)** (meaning _Ruby Spec_).
13
+ ## Goal
17
14
 
18
- This project is totally independent of [rspec.info](http://rspec.info/).
15
+ This clone attempts to provide most of RSpec's DSL without magic power.
19
16
 
20
- Also, while both gems define an `RSpec` module, **r_spec** (which follows [the gem naming convention](http://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words)) is still quite different than **rspec** due to its [Ruby Fix](http://fixrb.org/) dependency.
17
+ ## Some differences
21
18
 
22
- ***
19
+ * Less features and an implementation with much less code complexity.
20
+ * Spec files can also be executed directly with the `ruby` executable.
21
+ * There is no option to activate monkey-patching.
22
+ * Does not rely on hacks such as `at_exit` hook to trigger the tests.
23
+ * Built-in matchers do not trust _actual_ and do not send it any message.
24
+ * The subject must be explicitly defined, otherwise it is not implemented.
23
25
 
24
- ## Contact
26
+ ## Important ⚠️
25
27
 
26
- * Home page: https://github.com/cyril/r_spec
27
- * Bugs/issues: https://github.com/cyril/r_spec/issues
28
+ To avoid confusion in the community, please note that:
28
29
 
29
- ## Rubies
30
+ - the gem of this project is **not [`rspec`](https://rubygems.org/gems/rspec)**,
31
+ it is **[`r_spec`](https://rubygems.org/gems/r_spec)**;
32
+ - this project is totally independent of [rspec.info](https://rspec.info/).
30
33
 
31
- * [MRI](https://www.ruby-lang.org/)
32
- * [Rubinius](http://rubini.us/)
33
- * [JRuby](http://jruby.org/)
34
+ ### Note
34
35
 
35
- ## Terminal sessions
36
+ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words), the module name for this project is `RSpec`.
36
37
 
37
- A comparison between the behavior of a small fix-based script (which became the `r_spec` gem) and `rspec`.
38
+ ## Installation
38
39
 
39
- As a result, with `r_spec` the build is passing, while with rspec it is failing. Despite two separate contexts, `rspec` was not able to evaluate some code in isolation to prevent side effects.
40
+ Add this line to your application's Gemfile:
40
41
 
41
- [![What did you RSpec?](https://asciinema.org/a/29070.png)](https://asciinema.org/a/29070)
42
+ ```ruby
43
+ gem "r_spec", ">= 1.0.0.beta2"
44
+ ```
42
45
 
43
- ***
46
+ And then execute:
44
47
 
45
- Unbelievable but true, sometimes "💩" is equal to 42.
48
+ ```sh
49
+ bundle
50
+ ```
46
51
 
47
- Although fun, this _feature_ can be fixed pretty easily...
52
+ Or install it yourself as:
48
53
 
49
- [![Fix RSpec!](https://asciinema.org/a/29172.png)](https://asciinema.org/a/29172)
54
+ ```sh
55
+ gem install r_spec --pre
56
+ ```
50
57
 
51
- ## Installation
58
+ ## Usage
52
59
 
53
- __R Spec__ is cryptographically signed.
60
+ To understand how the framework builds and runs tests, here are some correspondences between the DSL syntax and the generated Ruby code.
54
61
 
55
- To be sure the gem you install hasn't been tampered with, add my public key (if you haven't already) as a trusted certificate:
62
+ ### `describe` method
56
63
 
57
- $ gem cert --add <(curl -Ls https://raw.github.com/cyril/r_spec/master/certs/gem-cyril-public_cert.pem)
58
- $ gem install r_spec -P HighSecurity
64
+ Example of specification content:
59
65
 
60
- The `HighSecurity` trust profile will verify all gems. All of __R Spec__'s dependencies are signed.
66
+ ```ruby
67
+ RSpec.describe String do
68
+ end
69
+ ```
61
70
 
62
- Or add this line to your application's Gemfile:
71
+ Corresponding Ruby code:
63
72
 
64
73
  ```ruby
65
- gem 'r_spec'
74
+ module RSpec::Test
75
+ class Test3582143298
76
+ protected
77
+
78
+ def described_class
79
+ String
80
+ end
81
+ end
82
+ end
66
83
  ```
67
84
 
68
- And then execute:
85
+ ### `context` method
69
86
 
70
- $ bundle
87
+ The behavior of the `context` method is exactly the same as `describe`.
71
88
 
72
- ## Usage
89
+ ### `subject` method
73
90
 
74
- Given this `greeting_spec.rb` spec:
91
+ Example of specification content:
75
92
 
76
93
  ```ruby
77
- require 'r_spec'
94
+ RSpec.describe "Subject" do
95
+ subject do
96
+ :foo
97
+ end
98
+ end
99
+ ```
78
100
 
79
- greeting = 'Hello, world!'
101
+ Corresponding Ruby code:
80
102
 
81
- RSpec.describe 'Test' do
82
- context 'Alice' do
83
- before { greeting.gsub!('world', 'Alice') }
84
- it { expect(greeting).to eql 'Hello, Alice!' }
103
+ ```ruby
104
+ module RSpec::Test
105
+ class Test3582143298
106
+ protected
107
+
108
+ def subject
109
+ :foo
110
+ end
85
111
  end
112
+ end
113
+ ```
114
+
115
+ ### Embedded `describe` method
116
+
117
+ Example of specification content:
86
118
 
87
- context 'Bob' do
88
- before { greeting.gsub!('world', 'Bob') }
89
- it { expect(greeting).to eql 'Hello, Bob!' }
119
+ ```ruby
120
+ RSpec.describe "Describe" do
121
+ # main describe block
122
+
123
+ describe "Embedded describe" do
124
+ # embedded describe block
125
+ end
126
+ end
127
+ ```
128
+
129
+ Corresponding Ruby code:
130
+
131
+ ```ruby
132
+ module RSpec::Test
133
+ class Test3582143298
134
+ # main describe block
135
+ end
136
+ end
137
+
138
+ module RSpec::Test
139
+ class Test198623541 < RSpec::Test::Test3582143298
140
+ # embedded describe block
141
+ end
142
+ end
143
+ ```
144
+
145
+ ### `let` method
146
+
147
+ Example of specification content:
148
+
149
+ ```ruby
150
+ RSpec.describe do
151
+ let(:var0) { 42 }
152
+ let(:var1) { 42 + var3 }
153
+ let(:var3) { 42 }
154
+ end
155
+ ```
156
+
157
+ Corresponding Ruby code:
158
+
159
+ ```ruby
160
+ module RSpec::Test
161
+ class Test3582143298
162
+ protected
163
+
164
+ def var0
165
+ 42
166
+ end
167
+
168
+ def var1
169
+ 42 + var3
170
+ end
171
+
172
+ def var3
173
+ 42
174
+ end
175
+ end
176
+ end
177
+ ```
178
+
179
+ ### `before` method
180
+
181
+ Example of specification content:
182
+
183
+ ```ruby
184
+ RSpec.describe do
185
+ before do
186
+ puts "hello"
187
+ end
188
+ end
189
+ ```
190
+
191
+ Corresponding Ruby code:
192
+
193
+ ```ruby
194
+ module RSpec::Test
195
+ class Test3582143298
196
+ def initialize
197
+ puts "hello"
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ ### `expect` method
204
+
205
+ Example of specification content:
206
+
207
+ ```ruby
208
+ RSpec.describe do
209
+ it { expect(41.next).to be(42) }
210
+ end
211
+ ```
212
+
213
+ Corresponding Ruby code:
214
+
215
+ ```ruby
216
+ module RSpec::Test
217
+ class Test3582143298
218
+ end
219
+ end
220
+
221
+ example_class = Class.new(RSpec::Test::Test3582143298) do
222
+ include Matchi::Helper
223
+
224
+ # Declaration of private methods (`expect`, `is_expected`, `log`, `pending`).
225
+ end
226
+
227
+ example_class.new.instance_eval do
228
+ ExpectationTarget::Value.new(41.next).to be(42)
229
+ end
230
+ ```
231
+
232
+ Success: expected to be 42.
233
+
234
+ ## Example
235
+
236
+ Let's test an array:
237
+
238
+ ```ruby
239
+ # array_spec.rb
240
+
241
+ require "r_spec"
242
+
243
+ RSpec.describe Array do
244
+ before do
245
+ @elements = described_class.new
246
+ end
247
+
248
+ describe "#count" do
249
+ subject do
250
+ @elements.count
251
+ end
252
+
253
+ it { is_expected.to be 0 }
254
+
255
+ context "when a new element is added" do
256
+ before do
257
+ @elements << 1
258
+ end
259
+
260
+ it { is_expected.to be 1 }
261
+ end
90
262
  end
91
263
  end
92
264
  ```
93
265
 
94
266
  It can be tested in the console with the command:
95
267
 
96
- $ ruby greeting_spec.rb
97
- ..
268
+ ```sh
269
+ ruby array_spec.rb
270
+ ```
98
271
 
99
- Ran 2 tests in 0.010994 seconds
100
- 100% compliant - 0 infos, 0 failures, 0 errors
272
+ array_spec.rb:15 Success: expected to be 0.
273
+ array_spec.rb:22 Success: expected to be 1.
101
274
 
102
- ## Security
275
+ ## Test suite
103
276
 
104
- As a basic form of security __R Spec__ provides a set of SHA512 checksums for
105
- every Gem release. These checksums can be found in the `checksum/` directory.
106
- Although these checksums do not prevent malicious users from tampering with a
107
- built Gem they can be used for basic integrity verification purposes.
277
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
108
278
 
109
- The checksum of a file can be checked using the `sha512sum` command. For
110
- example:
279
+ ## Contact
111
280
 
112
- $ sha512sum pkg/r_spec-0.1.0.gem
113
- e9e35e1953104e2d428b0f217e418db3c1baecd9e011b2545f9fcba4ff7e3bba674c6b928b3d8db842a139cd7cc9806d77ebdc7f710ece4f2aecb343703e2451 pkg/r_spec-0.1.0.gem
281
+ * Home page: https://r-spec.dev
282
+ * Source code: https://github.com/cyril/r_spec.rb
283
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
114
284
 
115
285
  ## Versioning
116
286
 
117
- __R Spec__ follows [Semantic Versioning 2.0](http://semver.org/).
287
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
118
288
 
119
- ## Contributing
289
+ ## Special thanks
120
290
 
121
- 1. [Fork it](https://github.com/cyril/r_spec/fork)
122
- 2. Create your feature branch (`git checkout -b my-new-feature`)
123
- 3. Commit your changes (`git commit -am 'Add some feature'`)
124
- 4. Push to the branch (`git push origin my-new-feature`)
125
- 5. Create a new Pull Request
291
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
292
+ It's a great framework and it's a pleasure to work with every day.
126
293
 
127
- ## License
294
+ Without RSpec, this clone would not have been possible. ❤️
128
295
 
129
- See `LICENSE.md` file.
296
+ ## License
130
297
 
131
- [gem]: https://rubygems.org/gems/r_spec
132
- [travis]: https://travis-ci.org/cyril/r_spec
133
- [codeclimate]: https://codeclimate.com/github/cyril/r_spec
134
- [gemnasium]: https://gemnasium.com/cyril/r_spec
135
- [inchpages]: http://inch-ci.org/github/cyril/r_spec
136
- [rubydoc]: http://rubydoc.info/gems/r_spec/frames
298
+ The [gem](https://rubygems.org/gems/r_spec) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/r_spec.rb CHANGED
@@ -1,32 +1,25 @@
1
- require 'matchi/rspec'
2
- require 'fix/expect'
3
- require 'fix/its'
4
- require 'fix/let'
1
+ # frozen_string_literal: true
5
2
 
6
- # Namespace for the R Spec framework.
3
+ # Top level namespace for the RSpec clone.
7
4
  #
8
- # @api public
5
+ # @example
6
+ # require "r_spec"
7
+ #
8
+ # RSpec.describe Integer do
9
+ # it { expect(41.next).to be 42 }
10
+ # end
9
11
  #
12
+ # @api public
10
13
  module RSpec
11
14
  # Specs are built with this method.
12
15
  #
13
- # @example The answer must be equal to 42.
14
- # describe('the answer') do
15
- # it { expect(42).to be 42 }
16
- # end
16
+ # @param const [Module, String] A module to include in block context.
17
+ # @param block [Proc] The block to define the specs.
17
18
  #
18
- # @param front_object [#object_id] The front object.
19
- # @param options [Hash] Some options.
20
- # @param specs [Proc] The set of specs.
21
- #
22
- # @raise [SystemExit] The result of the test.
23
- def self.describe(front_object, options = {}, &specs)
24
- t = ::Fix::Test.new(front_object, options, &specs)
25
-
26
- print t.report.to_s if options.fetch(:verbose, true)
27
- exit t.pass?
19
+ # @api public
20
+ def self.describe(const, &block)
21
+ DSL.describe(const, &block)
28
22
  end
29
23
  end
30
24
 
31
- require_relative File.join 'fix', 'it'
32
- require_relative File.join 'fix', 'on'
25
+ require_relative File.join("r_spec", "dsl")
data/lib/r_spec/dsl.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matchi/rspec"
4
+ require "securerandom"
5
+
6
+ module RSpec
7
+ # Abstract class for handling the domain-specific language.
8
+ class DSL
9
+ # @param block [Proc] The content to execute at the class initialization.
10
+ def self.before(&block)
11
+ define_method(:initialize) do |*args, **kwargs|
12
+ super()
13
+ instance_exec(*args, **kwargs, &block)
14
+ end
15
+ end
16
+
17
+ # @param block [Proc] The content of the method to define.
18
+ # @return [Symbol] A protected method that define the block content.
19
+ def self.let(name, &block)
20
+ protected define_method(name.to_sym, &block)
21
+ end
22
+
23
+ # @param block [Proc] The subject to set.
24
+ # @return [Symbol] A `subject` method that define the block content.
25
+ def self.subject(&block)
26
+ let(__method__, &block)
27
+ end
28
+
29
+ # Describe a set of expectations.
30
+ #
31
+ # @param const [Module, #object_id] A module to include in block context.
32
+ # @param block [Proc] The block to define the specs.
33
+ def self.describe(const, &block)
34
+ desc = Test.const_set(random_test_const_name, ::Class.new(self))
35
+
36
+ if const.is_a?(::Module)
37
+ desc.define_method(:described_class) { const }
38
+ desc.send(:protected, :described_class)
39
+ end
40
+
41
+ desc.instance_eval(&block)
42
+ desc
43
+ end
44
+
45
+ # Add `context` to the DSL.
46
+ singleton_class.send(:alias_method, :context, :describe)
47
+
48
+ # Evaluate an expectation.
49
+ #
50
+ # @param block [Proc] An expectation to evaluate.
51
+ #
52
+ # @raise (see ExpectationTarget::Base#result)
53
+ # @return (see ExpectationTarget::Base#result)
54
+ def self.it(_name = nil, &block)
55
+ raise ::ArgumentError, "Missing block" unless block
56
+
57
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
58
+
59
+ i = example.new
60
+ i.instance_eval(&block)
61
+ end
62
+
63
+ # @private
64
+ #
65
+ # @return [Class<DSL>] The class of the example to be tested.
66
+ def self.example
67
+ # Dynamic creation of an example class.
68
+ ::Class.new(self) do
69
+ # Include a collection of matchers.
70
+ include ::Matchi::Helper
71
+
72
+ private
73
+
74
+ # Wraps the target of an expectation with the actual value.
75
+ #
76
+ # @param actual [#object_id] The actual value.
77
+ #
78
+ # @return [ExpectationTarget] The target of the expectation.
79
+ def expect(actual = self.class.superclass, &block)
80
+ ExpectationTarget.call(self.class.superclass, actual, block)
81
+ end
82
+
83
+ # Wraps the target of an expectation with the subject as actual value.
84
+ #
85
+ # @return [ExpectationTarget] (see #expect)
86
+ def is_expected
87
+ expect(subject)
88
+ end
89
+
90
+ # Output a message to the console.
91
+ #
92
+ # @param message [String] The string to be notified about.
93
+ #
94
+ # @return [nil] Write a message to STDOUT.
95
+ def log(message)
96
+ Log.result(message)
97
+ end
98
+
99
+ # Indicate that an example is disabled pending some action.
100
+ #
101
+ # @param description [String] The reason why the example is pending.
102
+ #
103
+ # @return [nil] Write a message to STDOUT.
104
+ def pending(description)
105
+ Pending.result(description)
106
+ end
107
+ end
108
+ end
109
+
110
+ # @private
111
+ #
112
+ # @return [String] A random constant name for a test class.
113
+ def self.random_test_const_name
114
+ "Test#{::SecureRandom.hex(4).to_i(16)}"
115
+ end
116
+
117
+ private_class_method :example, :random_test_const_name
118
+ end
119
+ end
120
+
121
+ require_relative "expectation_target"
122
+ require_relative "log"
123
+ require_relative "pending"
124
+ require_relative "test"