brutal 0.4.0 → 1.2.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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +53 -120
- data/bin/brutal +2 -41
- data/lib/brutal.rb +19 -6
- data/lib/brutal/configuration.rb +50 -0
- data/lib/brutal/file/read.rb +33 -0
- data/lib/brutal/file/write.rb +36 -0
- data/lib/brutal/scaffold.rb +91 -0
- data/lib/brutal/yaml.rb +14 -0
- metadata +54 -24
- data/lib/brutal/scaffold_generator/base.rb +0 -30
- data/lib/brutal/scaffold_generator/por.rb +0 -52
- data/lib/brutal/scaffold_generator/rspec.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a7eb1b01e30dda0f5b204eef32ac2ec59c19384b37500f40587a1a522fce6db
|
4
|
+
data.tar.gz: be19e4c8f4741658ef2aea98891b8bf040a250361fe48fee5219b02cc297b10f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 565a5d6628dd7f3904f97343da33539eaddf97c6fa020d2c14736ed84a146c3fe3d0aa7eb6fdfc73a94a3238be3bba40d892f07281d231918022b2c96fb95ce6
|
7
|
+
data.tar.gz: 245f56b40a04d626c653e679b3634a5e1f3805a4f356c6f009759cb9e350e675b89e17e699e430fac5ffcb596e83c19190b929b4086b1da42254f2ffdbc7ea60
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,84 +1,44 @@
|
|
1
1
|
# Brutal 💎🔨
|
2
2
|
|
3
|
-
[][workflow_rubocop]
|
4
|
+
[][travis]
|
4
5
|
[][gem]
|
5
|
-
[][inchpages]
|
6
7
|
[][rubydoc]
|
7
8
|
|
8
9
|
> A _code-first_ approach to automate the writing of unit tests.
|
9
10
|
|
10
11
|
## Intro
|
11
12
|
|
12
|
-
[](https://www.youtube.com/embed/cmOt9HhszCI?start=1732&end=1736 "I don't like tests. It's not DRY.")
|
13
14
|
|
14
15
|
> I don't like tests. It's not DRY.<br/>
|
15
16
|
> -- [Matz](https://github.com/matz)
|
16
17
|
|
17
|
-
##
|
18
|
+
## Overview
|
18
19
|
|
19
|
-
|
20
|
+
Let __Brutal__ shape for you in no time the actual behavior of your code through as many combinations of contexts as needed.
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
By delegating to __Brutal__ the repetitive and redundant task of writing tests, you'll be able to focus on your core business: writing code.
|
22
|
+
By delegating to __Brutal__ the repetitive (and redundant) task of writing tests, you'll be able to focus on your core business: the code itself.
|
24
23
|
|
25
24
|
## Warning
|
26
25
|
|
27
|
-
__Brutal__ does not prevent from bugs.
|
28
|
-
As a picture of the behavior of the code, its generated tests would be wrong if the code is wrong.
|
29
|
-
|
30
|
-
This is why it is important to carefully read the generated test suite, to ensure that it describes the behavior of the code as it is supposed to behave.
|
31
|
-
|
32
|
-
However, when the `brutal` command is executed successfully,
|
33
|
-
we can consider that both __Brutal__'s configuration file (`.brutal.yml`) and evaluated code as syntactically correct.
|
34
|
-
|
35
|
-
In the context of a versioned project, to avoid regressions,
|
36
|
-
the integrity of the behavior of the code can easily be checked by re-generating a picture to be compared with the previous one.
|
37
|
-
Here is an example with [The Greeter class](https://github.com/fixrb/brutal/raw/master/examples/the_greeter_class/) code:
|
38
|
-
|
39
|
-
```sh
|
40
|
-
git diff
|
41
|
-
```
|
42
|
-
|
43
|
-
The code:
|
44
|
-
|
45
|
-
```diff
|
46
|
-
# The Greeter class
|
47
|
-
class Greeter
|
48
|
-
def initialize(name)
|
49
|
-
- @name = name
|
50
|
-
+ @name = name.capitalize
|
51
|
-
end
|
52
|
-
|
53
|
-
def salute
|
54
|
-
"Hello #{@name}!"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
```
|
58
|
-
|
59
|
-
The generated test of the code:
|
60
|
-
|
61
|
-
```diff
|
62
|
-
require './greeter'
|
63
|
-
|
64
|
-
# ------------------------------------------------------------------------------
|
65
|
-
|
66
|
-
front_object = Greeter
|
26
|
+
__Brutal__ development process does not prevent from bugs.
|
67
27
|
|
68
|
-
|
28
|
+
As a _picture of the behavior of the code_,
|
29
|
+
a generated test suite is wrong as long as the code is wrong,
|
30
|
+
regardless of whether all true expectations.
|
69
31
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
+ raise unless actual.salute == "Hello World!"
|
74
|
-
```
|
32
|
+
However, this document becomes relevant when it shows that the code behaves as it is supposed to.
|
33
|
+
It is therefore important to read it well.
|
34
|
+
This is the price for _Brutal-Driven Development_.
|
75
35
|
|
76
36
|
## Installation
|
77
37
|
|
78
38
|
Add this line to your application's Gemfile:
|
79
39
|
|
80
40
|
```ruby
|
81
|
-
gem
|
41
|
+
gem "brutal"
|
82
42
|
```
|
83
43
|
|
84
44
|
And then execute:
|
@@ -95,105 +55,77 @@ Just type `brutal` in a Ruby project's folder and watch the magic happen.
|
|
95
55
|
|
96
56
|
## Usage
|
97
57
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
* `header` (optional): Some code to execute before the test suite.
|
102
|
-
* `front_object` (required): The front object of the test suite.
|
103
|
-
* `subject` (required): The object of each context.
|
104
|
-
* `variables` (required): A hash to generate the subject of each context.
|
105
|
-
* `actual_values` (required): A list of tests to challenge the subject.
|
106
|
-
|
107
|
-
### Optional parameters
|
58
|
+
The `brutal.yml` file is a manifest you can use to define your __Brutal__ meta-spec.
|
59
|
+
It has 4 top-level sections:
|
108
60
|
|
109
|
-
|
61
|
+
* `header` - Specifies the code to execute before generating the test suite.
|
62
|
+
* `subject` - Specifies the template of the code to be declined across contexts.
|
63
|
+
* `contexts` - Specifies a list of variables to populate the subject's template.
|
64
|
+
* `actuals` - Specifies templates to challenge evaluated subjects & get results.
|
110
65
|
|
111
|
-
|
66
|
+
### Getting started
|
112
67
|
|
113
|
-
|
114
|
-
|
115
|
-
Given this config file:
|
68
|
+
1. Create a `brutal.yml` file in your application's root directory.
|
69
|
+
The following example `brutal.yml` defines the shape of a Hello test suite:
|
116
70
|
|
117
71
|
```yaml
|
118
72
|
---
|
119
|
-
front_object: |
|
120
|
-
"Hello "
|
121
|
-
|
122
73
|
subject: |
|
123
|
-
|
74
|
+
"Hello " + "%{string}"
|
124
75
|
|
125
|
-
|
76
|
+
contexts:
|
126
77
|
string:
|
127
|
-
-
|
128
|
-
-
|
78
|
+
- Alice
|
79
|
+
- Bob
|
129
80
|
|
130
|
-
|
81
|
+
actuals:
|
131
82
|
- "%{subject}.to_s"
|
132
83
|
- "%{subject}.length"
|
133
84
|
```
|
134
85
|
|
135
|
-
|
86
|
+
2. Run the `brutal` command from the same directory.
|
136
87
|
|
137
|
-
|
138
|
-
front_object = "Hello "
|
139
|
-
|
140
|
-
# ------------------------------------------------------------------------------
|
88
|
+
3. Read the generated `test.rb` file in the same directory:
|
141
89
|
|
142
|
-
|
143
|
-
|
144
|
-
raise unless actual.length == 11
|
90
|
+
```ruby
|
91
|
+
# Brutal test suite
|
145
92
|
|
146
93
|
# ------------------------------------------------------------------------------
|
147
94
|
|
148
|
-
actual =
|
149
|
-
|
150
|
-
|
151
|
-
```
|
152
|
-
|
153
|
-
And the `brutal rspec` command would generate and write in to a `test_spec.rb` file the following spec:
|
154
|
-
|
155
|
-
```ruby
|
156
|
-
RSpec.describe do
|
157
|
-
let(:front_object) { "Hello " }
|
158
|
-
|
159
|
-
context do
|
160
|
-
let(:actual) { front_object + 'Alice' }
|
95
|
+
actual = begin
|
96
|
+
"Hello " + "Alice"
|
97
|
+
end
|
161
98
|
|
162
|
-
|
163
|
-
|
164
|
-
end
|
99
|
+
raise if actual.to_s != "Hello Alice"
|
100
|
+
raise if actual.length != 11
|
165
101
|
|
166
|
-
|
167
|
-
let(:actual) { front_object + 'Bob' }
|
102
|
+
# ------------------------------------------------------------------------------
|
168
103
|
|
169
|
-
|
170
|
-
|
171
|
-
end
|
104
|
+
actual = begin
|
105
|
+
"Hello " + "Bob"
|
172
106
|
end
|
107
|
+
|
108
|
+
raise if actual.to_s != "Hello Bob"
|
109
|
+
raise if actual.length != 9
|
173
110
|
```
|
174
111
|
|
175
|
-
More examples
|
112
|
+
### More examples
|
113
|
+
|
114
|
+
https://github.com/fixrb/brutal/raw/main/examples/
|
176
115
|
|
177
116
|
## Rake integration example
|
178
117
|
|
179
|
-
|
118
|
+
A generated `test.rb` file could be matched as follows:
|
180
119
|
|
181
120
|
```ruby
|
182
121
|
Rake::TestTask.new do |t|
|
183
|
-
t.pattern =
|
122
|
+
t.pattern = "test.rb"
|
184
123
|
end
|
185
124
|
```
|
186
125
|
|
187
126
|
## Contact
|
188
127
|
|
189
|
-
*
|
190
|
-
* Bugs/issues: https://github.com/fixrb/brutal/issues
|
191
|
-
|
192
|
-
## Rubies
|
193
|
-
|
194
|
-
* [MRI](https://www.ruby-lang.org/)
|
195
|
-
* [Rubinius](https://rubinius.com/)
|
196
|
-
* [JRuby](https://www.jruby.org/)
|
128
|
+
* Source code: https://github.com/fixrb/brutal
|
197
129
|
|
198
130
|
## Versioning
|
199
131
|
|
@@ -201,17 +133,18 @@ __Brutal__ follows [Semantic Versioning 2.0](https://semver.org/).
|
|
201
133
|
|
202
134
|
## License
|
203
135
|
|
204
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
136
|
+
The [gem](https://rubygems.org/gems/brutal) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
205
137
|
|
206
138
|
***
|
207
139
|
|
208
140
|
<p>
|
209
141
|
This project is sponsored by:<br />
|
210
142
|
<a href="https://sashite.com/"><img
|
211
|
-
src="https://github.com/fixrb/brutal/raw/
|
143
|
+
src="https://github.com/fixrb/brutal/raw/main/img/sashite.png"
|
212
144
|
alt="Sashite" /></a>
|
213
145
|
</p>
|
214
146
|
|
147
|
+
[workflow_rubocop]: https://github.com/fixrb/brutal/actions?query=workflow%3ARuboCop
|
215
148
|
[gem]: https://rubygems.org/gems/brutal
|
216
149
|
[travis]: https://travis-ci.org/fixrb/brutal
|
217
150
|
[inchpages]: https://inch-ci.org/github/fixrb/brutal
|
data/bin/brutal
CHANGED
@@ -1,45 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
require_relative File.join("..", "lib", "brutal")
|
5
5
|
|
6
|
-
|
7
|
-
CONF_PATH = ::File.join(::Dir.pwd, FILE_NAME)
|
8
|
-
|
9
|
-
abort "File #{CONF_PATH} not found!" unless ::File.exist?(CONF_PATH)
|
10
|
-
|
11
|
-
conf = ::YAML.load_file(CONF_PATH)
|
12
|
-
|
13
|
-
header = conf.fetch('header', '').to_s.chomp
|
14
|
-
front_object = conf.fetch('front_object')
|
15
|
-
subject = conf.fetch('subject')
|
16
|
-
variables = conf.fetch('variables', {})
|
17
|
-
actual_values = conf.fetch('actual_values')
|
18
|
-
|
19
|
-
raise ::TypeError unless subject.is_a?(::String)
|
20
|
-
raise ::TypeError unless variables.is_a?(::Hash)
|
21
|
-
raise ::TypeError unless actual_values.is_a?(::Array)
|
22
|
-
|
23
|
-
front_object = front_object.chomp if front_object.is_a?(::String)
|
24
|
-
|
25
|
-
require_relative ::File.join('..', 'lib', 'brutal')
|
26
|
-
|
27
|
-
scaffold, file_name = case ARGV.fetch(0, nil)
|
28
|
-
when nil
|
29
|
-
[::Brutal::ScaffoldGenerator::Por, 'test.rb']
|
30
|
-
when 'rspec'
|
31
|
-
[::Brutal::ScaffoldGenerator::RSpec, 'test_spec.rb']
|
32
|
-
else
|
33
|
-
abort "#{ARGV[0].inspect} not (yet) supported!"
|
34
|
-
end
|
35
|
-
|
36
|
-
eval(header) # rubocop:disable Security/Eval
|
37
|
-
|
38
|
-
doc = [
|
39
|
-
header,
|
40
|
-
scaffold.new(front_object, subject, *actual_values, **variables).to_s
|
41
|
-
].reject(&:empty?).join("\n\n")
|
42
|
-
|
43
|
-
file = ::File.open(file_name, 'w')
|
44
|
-
file.write(doc)
|
45
|
-
file.close
|
6
|
+
Brutal.generate!
|
data/lib/brutal.rb
CHANGED
@@ -1,11 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
%w[
|
4
|
+
configuration
|
5
|
+
file/read
|
6
|
+
file/write
|
7
|
+
scaffold
|
8
|
+
yaml
|
9
|
+
].each { |file_name| require_relative(File.join("brutal", file_name)) }
|
10
|
+
|
11
|
+
# The Brutal namespace.
|
6
12
|
module Brutal
|
7
|
-
|
13
|
+
def self.generate!
|
14
|
+
yaml = File::Read.new.call
|
15
|
+
hash = Yaml.parse(yaml)
|
16
|
+
conf = Configuration.load(hash)
|
17
|
+
ruby = Scaffold.new(conf.header,
|
18
|
+
conf.subject,
|
19
|
+
*conf.actuals,
|
20
|
+
**conf.contexts)
|
8
21
|
|
9
|
-
|
10
|
-
|
22
|
+
File::Write.new.call(ruby)
|
23
|
+
end
|
11
24
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Brutal
|
4
|
+
# Brutal::Configuration
|
5
|
+
#
|
6
|
+
# @since 1.0.0
|
7
|
+
class Configuration
|
8
|
+
DEFAULT_ACTUALS = [].freeze
|
9
|
+
DEFAULT_CONTEXTS = {}.freeze
|
10
|
+
DEFAULT_HEAD = "# Brutal test suite"
|
11
|
+
DEFAULT_SUBJECT = ""
|
12
|
+
|
13
|
+
# Load the configuration parameters.
|
14
|
+
#
|
15
|
+
# @param params [Hash] Receive the 4 top-level section parameters.
|
16
|
+
def self.load(params)
|
17
|
+
new(
|
18
|
+
actuals: params.fetch("actuals", DEFAULT_ACTUALS),
|
19
|
+
contexts: params.fetch("contexts", DEFAULT_CONTEXTS),
|
20
|
+
header: params.fetch("header", DEFAULT_HEAD),
|
21
|
+
subject: params.fetch("subject", DEFAULT_SUBJECT)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Specifies templates to challenge evaluated subjects & get results.
|
26
|
+
attr_reader :actuals
|
27
|
+
|
28
|
+
# Specifies a list of variables to populate the subject's template.
|
29
|
+
attr_reader :contexts
|
30
|
+
|
31
|
+
# Specifies the code to execute before generating the test suite.
|
32
|
+
attr_reader :header
|
33
|
+
|
34
|
+
# Specifies the template of the code to be declined across contexts.
|
35
|
+
attr_reader :subject
|
36
|
+
|
37
|
+
# Initialize a new configuration.
|
38
|
+
def initialize(actuals:, contexts:, header:, subject:)
|
39
|
+
raise ::TypeError, actuals.inspect unless actuals.is_a?(::Array)
|
40
|
+
raise ::TypeError, contexts.inspect unless contexts.is_a?(::Hash)
|
41
|
+
raise ::TypeError, header.inspect unless header.is_a?(::String)
|
42
|
+
raise ::TypeError, subject.inspect unless subject.is_a?(::String)
|
43
|
+
|
44
|
+
@actuals = actuals.sort
|
45
|
+
@contexts = contexts
|
46
|
+
@header = header
|
47
|
+
@subject = subject
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Brutal
|
4
|
+
# Brutal::File
|
5
|
+
#
|
6
|
+
# @since 1.1.0
|
7
|
+
module File
|
8
|
+
# Brutal::File::Read
|
9
|
+
#
|
10
|
+
# @since 1.1.0
|
11
|
+
class Read
|
12
|
+
NAME = ".brutal.yml"
|
13
|
+
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
def initialize(name = NAME)
|
17
|
+
@name = name
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
::File.read(path)
|
22
|
+
rescue ::Errno::ENOENT => _e
|
23
|
+
abort("File #{path} not found!")
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def path
|
29
|
+
::File.join(::Dir.pwd, name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Brutal
|
4
|
+
# Brutal::File
|
5
|
+
#
|
6
|
+
# @since 1.1.0
|
7
|
+
module File
|
8
|
+
# Brutal::File::Write
|
9
|
+
#
|
10
|
+
# @since 1.1.0
|
11
|
+
class Write
|
12
|
+
NAME = "test.rb"
|
13
|
+
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
def initialize(name = NAME)
|
17
|
+
@name = name
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(scaffold)
|
21
|
+
file = ::File.open(path, "w")
|
22
|
+
file.write(scaffold)
|
23
|
+
|
24
|
+
true
|
25
|
+
ensure
|
26
|
+
file.close
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def path
|
32
|
+
::File.join(::Dir.pwd, name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Brutal
|
4
|
+
# Brutal::Scaffold
|
5
|
+
#
|
6
|
+
# @since 1.0.0
|
7
|
+
class Scaffold
|
8
|
+
# Specifies templates to challenge evaluated subjects & get results.
|
9
|
+
attr_reader :actuals
|
10
|
+
|
11
|
+
# Specifies a list of variables to populate the subject's template.
|
12
|
+
attr_reader :contexts
|
13
|
+
|
14
|
+
# Specifies the code to execute before generating the test suite.
|
15
|
+
attr_reader :header
|
16
|
+
|
17
|
+
# Specifies the template of the code to be declined across contexts.
|
18
|
+
attr_reader :subject
|
19
|
+
|
20
|
+
# Initialize a new scaffold generator.
|
21
|
+
def initialize(header, subject, *actuals, **contexts)
|
22
|
+
warn("Empty subject!") if subject.empty?
|
23
|
+
warn("Empty actual values!") if actuals.empty?
|
24
|
+
warn("Empty contexts!") if contexts.empty?
|
25
|
+
|
26
|
+
eval(header) # rubocop:disable Security/Eval
|
27
|
+
|
28
|
+
@header = header
|
29
|
+
@subject = subject
|
30
|
+
@actuals = actuals
|
31
|
+
@contexts = contexts
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return a Ruby string that can be evaluated.
|
35
|
+
def inspect(object)
|
36
|
+
return object.to_s unless object.is_a?(::String)
|
37
|
+
|
38
|
+
object.strip
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a string representation.
|
42
|
+
#
|
43
|
+
# @return [String]
|
44
|
+
#
|
45
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
46
|
+
def to_s
|
47
|
+
"#{header.chomp}\n#{blank_line}" + combinations_values.map do |values|
|
48
|
+
attributes = context_names.each_with_index.inject({}) do |h, (name, i)|
|
49
|
+
h.merge(name.to_sym => inspect(values.fetch(i)))
|
50
|
+
end
|
51
|
+
|
52
|
+
actual_str = format(inspect(subject), **attributes)
|
53
|
+
|
54
|
+
string = <<~CODE
|
55
|
+
actual = begin
|
56
|
+
#{actual_str.gsub(/^/, ' ')}
|
57
|
+
end
|
58
|
+
|
59
|
+
CODE
|
60
|
+
|
61
|
+
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
62
|
+
|
63
|
+
actuals.each do |actual_value|
|
64
|
+
result_str = format(actual_value, subject: "actual")
|
65
|
+
string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
66
|
+
end
|
67
|
+
|
68
|
+
string
|
69
|
+
end.join(blank_line)
|
70
|
+
end
|
71
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
72
|
+
|
73
|
+
def blank_line
|
74
|
+
"\n" \
|
75
|
+
"# #{'-' * 78}\n" \
|
76
|
+
"\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
def context_names
|
80
|
+
contexts.keys.sort
|
81
|
+
end
|
82
|
+
|
83
|
+
def contexts_values
|
84
|
+
context_names.map { |context_name| contexts.fetch(context_name) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def combinations_values
|
88
|
+
Array(contexts_values[0]).product(*Array(contexts_values[1..]))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/brutal/yaml.rb
ADDED
metadata
CHANGED
@@ -1,45 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brutal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name: rubocop
|
42
|
+
name: rubocop-md
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -66,34 +66,62 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-thread_safety
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: simplecov
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
72
100
|
requirements:
|
73
|
-
- - "
|
101
|
+
- - ">="
|
74
102
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0
|
103
|
+
version: '0'
|
76
104
|
type: :development
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
|
-
- - "
|
108
|
+
- - ">="
|
81
109
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0
|
110
|
+
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: yard
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
86
114
|
requirements:
|
87
|
-
- - "
|
115
|
+
- - ">="
|
88
116
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0
|
117
|
+
version: '0'
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
|
-
- - "
|
122
|
+
- - ">="
|
95
123
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0
|
124
|
+
version: '0'
|
97
125
|
description: A code-first approach to automate the writing of unit tests.
|
98
126
|
email: contact@cyril.email
|
99
127
|
executables:
|
@@ -105,9 +133,11 @@ files:
|
|
105
133
|
- README.md
|
106
134
|
- bin/brutal
|
107
135
|
- lib/brutal.rb
|
108
|
-
- lib/brutal/
|
109
|
-
- lib/brutal/
|
110
|
-
- lib/brutal/
|
136
|
+
- lib/brutal/configuration.rb
|
137
|
+
- lib/brutal/file/read.rb
|
138
|
+
- lib/brutal/file/write.rb
|
139
|
+
- lib/brutal/scaffold.rb
|
140
|
+
- lib/brutal/yaml.rb
|
111
141
|
homepage: https://github.com/fixrb/brutal
|
112
142
|
licenses:
|
113
143
|
- MIT
|
@@ -120,14 +150,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
150
|
requirements:
|
121
151
|
- - ">="
|
122
152
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
153
|
+
version: 2.7.0
|
124
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
155
|
requirements:
|
126
156
|
- - ">="
|
127
157
|
- !ruby/object:Gem::Version
|
128
158
|
version: '0'
|
129
159
|
requirements: []
|
130
|
-
rubygems_version: 3.1.
|
160
|
+
rubygems_version: 3.1.4
|
131
161
|
signing_key:
|
132
162
|
specification_version: 4
|
133
163
|
summary: A code-first approach to automate the writing of unit tests.
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Brutal
|
4
|
-
module ScaffoldGenerator
|
5
|
-
# Base class
|
6
|
-
class Base
|
7
|
-
# @api private
|
8
|
-
attr_reader :front_object_str, :front_object, :subject, :actual_values, :variables
|
9
|
-
|
10
|
-
# Initialize a new scaffold generator
|
11
|
-
def initialize(front_object, subject, *actual_values, **variables)
|
12
|
-
raise 'Empty actual values!' if actual_values.empty?
|
13
|
-
raise 'Empty variables!' if variables.empty?
|
14
|
-
|
15
|
-
@front_object_str = inspect(front_object)
|
16
|
-
@front_object = eval(@front_object_str) # rubocop:disable Security/Eval
|
17
|
-
@subject = subject
|
18
|
-
@actual_values = actual_values
|
19
|
-
@variables = variables
|
20
|
-
end
|
21
|
-
|
22
|
-
# Return a Ruby string that can be evaluated.
|
23
|
-
def inspect(object)
|
24
|
-
return object.to_s unless object.is_a?(::String)
|
25
|
-
|
26
|
-
object.strip
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'base'
|
4
|
-
|
5
|
-
module Brutal
|
6
|
-
module ScaffoldGenerator
|
7
|
-
# Plain Old Ruby.
|
8
|
-
#
|
9
|
-
# @api private
|
10
|
-
#
|
11
|
-
class Por < Base
|
12
|
-
# Return a string representation
|
13
|
-
#
|
14
|
-
# @return [String]
|
15
|
-
#
|
16
|
-
# @api public
|
17
|
-
def to_s
|
18
|
-
names = variables.keys.sort
|
19
|
-
values_arr = names.map { |name| variables.fetch(name) }
|
20
|
-
|
21
|
-
test_params = Array(values_arr[0]).product(*Array(values_arr[1..-1]))
|
22
|
-
|
23
|
-
blank_line.gsub(/\A\n/, '') +
|
24
|
-
"front_object = #{front_object_str}\n" +
|
25
|
-
blank_line +
|
26
|
-
test_params.map do |values|
|
27
|
-
attributes = names.each_with_index.inject(front_object: 'front_object') do |h, (name, i)|
|
28
|
-
h.merge(name.to_sym => inspect(values.fetch(i)))
|
29
|
-
end
|
30
|
-
|
31
|
-
actual_str = format(inspect(subject), **attributes)
|
32
|
-
|
33
|
-
string = "actual = #{actual_str}\n\n"
|
34
|
-
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
35
|
-
|
36
|
-
actual_values.each do |actual_value|
|
37
|
-
result_str = format(actual_value, subject: 'actual')
|
38
|
-
string += "raise unless #{result_str} == #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
39
|
-
end
|
40
|
-
|
41
|
-
string
|
42
|
-
end.join(blank_line)
|
43
|
-
end
|
44
|
-
|
45
|
-
def blank_line
|
46
|
-
"\n" \
|
47
|
-
"# #{('-' * 78)}\n" \
|
48
|
-
"\n"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'base'
|
4
|
-
|
5
|
-
module Brutal
|
6
|
-
module ScaffoldGenerator
|
7
|
-
# Plain Old Ruby.
|
8
|
-
#
|
9
|
-
# @api private
|
10
|
-
#
|
11
|
-
class RSpec < Base
|
12
|
-
# Return a string representation
|
13
|
-
#
|
14
|
-
# @return [String]
|
15
|
-
#
|
16
|
-
# @api public
|
17
|
-
def to_s
|
18
|
-
names = variables.keys.sort
|
19
|
-
values_arr = names.map { |name| variables.fetch(name) }
|
20
|
-
|
21
|
-
test_params = Array(values_arr[0]).product(*Array(values_arr[1..-1]))
|
22
|
-
|
23
|
-
"RSpec.describe do\n" \
|
24
|
-
" let(:front_object) { #{front_object_str} }\n" \
|
25
|
-
"\n" +
|
26
|
-
test_params.map do |values|
|
27
|
-
string = " context do\n"
|
28
|
-
|
29
|
-
attributes = names.each_with_index.inject(front_object: 'front_object') do |h, (name, i)|
|
30
|
-
h.merge(name.to_sym => inspect(values.fetch(i)))
|
31
|
-
end
|
32
|
-
|
33
|
-
actual_str = format(inspect(subject), **attributes)
|
34
|
-
|
35
|
-
string += " let(:actual) { #{actual_str} }\n\n"
|
36
|
-
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
37
|
-
|
38
|
-
actual_values.each do |actual_value|
|
39
|
-
result_str = format(actual_value, subject: 'actual')
|
40
|
-
string += " it { expect(#{result_str}).to eq(#{eval(result_str).inspect}) }\n" # rubocop:disable Security/Eval
|
41
|
-
end
|
42
|
-
|
43
|
-
string += " end\n"
|
44
|
-
|
45
|
-
string
|
46
|
-
end.join(blank_line) +
|
47
|
-
"end\n"
|
48
|
-
end
|
49
|
-
|
50
|
-
def blank_line
|
51
|
-
"\n"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|