brutal 1.5.0 → 1.6.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -35
- data/bin/brutal +9 -7
- data/lib/brutal/command_line_arguments_parser.rb +102 -0
- data/lib/brutal/configuration.rb +1 -1
- data/lib/brutal/file/read.rb +1 -1
- data/lib/brutal/file/write.rb +1 -1
- data/lib/brutal/file.rb +1 -15
- data/lib/brutal/format/ruby.rb +113 -0
- data/lib/brutal/format.rb +16 -0
- data/lib/brutal/yaml.rb +3 -8
- data/lib/brutal.rb +22 -15
- metadata +7 -5
- data/lib/brutal/scaffold.rb +0 -113
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37fdb51264d859c50519c1bfc0f511542eb8bcca0817049e11191977d06cb7b1
|
4
|
+
data.tar.gz: 54e26e7e3ad8cc403fdb6c1db116186c61d06b0766eb8fa5e96ed7688913872b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d42f48f5e24be39950a9e5a1bfb521a84a37402b8cb3cca1eb680767ce63410ab44b836310ba7792af80a8d9c9f803ba30c9e08dff4f1094fde184409f81c95c
|
7
|
+
data.tar.gz: 40a6cf45ccdc12816d677fd5c8d19ed4c6305ec135c66fc2e56bd11f595291d9c16715b979ff920b1ed36cc90bddfe783db3653fb7f072cbc77ef8a0d760c769
|
data/README.md
CHANGED
@@ -38,7 +38,7 @@ It is therefore the responsibility of the developer to analyze the generated beh
|
|
38
38
|
Add this line to your application's Gemfile:
|
39
39
|
|
40
40
|
```ruby
|
41
|
-
gem "brutal"
|
41
|
+
gem "brutal", ">= 1.6.0.beta1", require: false
|
42
42
|
```
|
43
43
|
|
44
44
|
And then execute:
|
@@ -50,18 +50,31 @@ bundle install
|
|
50
50
|
Or install it yourself as:
|
51
51
|
|
52
52
|
```sh
|
53
|
-
gem install brutal
|
53
|
+
gem install brutal --pre
|
54
54
|
```
|
55
55
|
|
56
|
-
##
|
56
|
+
## Command line
|
57
57
|
|
58
|
-
|
58
|
+
The `brutal` command comes with several options you can use to customize Brutal's behavior.
|
59
|
+
|
60
|
+
For a full list of options, run the `brutal` command with the `--help` flag:
|
61
|
+
|
62
|
+
```sh
|
63
|
+
brutal --help
|
64
|
+
```
|
65
|
+
|
66
|
+
```txt
|
67
|
+
Usage: #{$PROGRAM_NAME} [options] [files or directories]
|
68
|
+
|
69
|
+
--format=FORMAT Choose "ruby" (default).
|
70
|
+
--help Display this help.
|
71
|
+
--version Display the version.
|
72
|
+
```
|
59
73
|
|
60
74
|
## Usage
|
61
75
|
|
62
|
-
__Brutal__ needs
|
63
|
-
|
64
|
-
This file is by default named `.brutal.yml` and is composed of 4 top-level sections:
|
76
|
+
__Brutal__ needs configuration files in YAML format to know how to write tests.
|
77
|
+
Configuration file names are suffixed by `_brutal.yaml` and composed of 4 top-level sections:
|
65
78
|
|
66
79
|
* `header` - Specifies the code to execute before generating the test suite.
|
67
80
|
* `subject` - Specifies the template of the code to be declined across contexts.
|
@@ -70,8 +83,10 @@ This file is by default named `.brutal.yml` and is composed of 4 top-level secti
|
|
70
83
|
|
71
84
|
When the configuration file is present, the generation of a test suite can be done with the command:
|
72
85
|
|
86
|
+
Assuming that in the workspace there is a configuration file named `user_brutal.yaml`, the test suite can be generated via one of these commands:
|
87
|
+
|
73
88
|
```sh
|
74
|
-
brutal .
|
89
|
+
brutal user_brutal.yaml
|
75
90
|
```
|
76
91
|
|
77
92
|
or:
|
@@ -86,48 +101,28 @@ or even:
|
|
86
101
|
brutal
|
87
102
|
```
|
88
103
|
|
89
|
-
This would create a `
|
90
|
-
|
91
|
-
Configuration files can also be named differently:
|
92
|
-
|
93
|
-
```sh
|
94
|
-
brutal path/to/test_hello_world.yml
|
95
|
-
```
|
96
|
-
|
97
|
-
This would create a `path/to/test_hello_world.rb` file containing the test suite.
|
104
|
+
This would create a `user_brutal.rb` file containing the test suite.
|
98
105
|
|
99
|
-
|
106
|
+
Assuming now that in the workspace there are a large number of configuration files named in the `spec/` folder, the complete test suite could be generated recursively via this command:
|
100
107
|
|
101
108
|
```sh
|
102
|
-
brutal
|
109
|
+
brutal spec/ # => generate tests from each configuration file matching ./spec/**/*_brutal.yaml in to ./spec/**/*_brutal.rb
|
103
110
|
```
|
104
111
|
|
105
|
-
|
106
|
-
|
107
|
-
### Getting started
|
112
|
+
### Some examples
|
108
113
|
|
109
|
-
|
110
|
-
2. Run the `brutal` command from the same directory.
|
111
|
-
3. Read the generated `test.rb` file in the same directory: <https://github.com/fixrb/brutal/blob/v1.4.0/examples/hello_world_v1/test.rb>
|
112
|
-
|
113
|
-
### More examples
|
114
|
-
|
115
|
-
<https://github.com/fixrb/brutal/blob/v1.4.0/examples/>
|
114
|
+
<https://github.com/fixrb/brutal/blob/v1.6.0.beta1/examples/>
|
116
115
|
|
117
116
|
## Rake integration example
|
118
117
|
|
119
|
-
|
118
|
+
Generated test suite files could be matched as follows:
|
120
119
|
|
121
120
|
```ruby
|
122
121
|
Rake::TestTask.new do |t|
|
123
|
-
t.pattern = "
|
122
|
+
t.pattern = "**/*_brutal.rb"
|
124
123
|
end
|
125
124
|
```
|
126
125
|
|
127
|
-
## Test suite
|
128
|
-
|
129
|
-
__Brutal__'s test set is brutally self-generated here: [./test.rb](https://github.com/fixrb/brutal/blob/main/test.rb)
|
130
|
-
|
131
126
|
## Contact
|
132
127
|
|
133
128
|
* Source code: https://github.com/fixrb/brutal
|
data/bin/brutal
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
require_relative File.join("..", "lib", "brutal", "command_line_arguments_parser")
|
5
|
+
format, pathnames = Brutal::CommandLineArgumentsParser.new(*ARGV).call
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
pathname += Brutal::File::DEFAULT_CONFIG_FILENAME if pathname.directory?
|
10
|
-
force_opt = ARGV.none?("--no-force")
|
7
|
+
require_relative File.join("..", "lib", "brutal")
|
8
|
+
generator = Brutal.new(format: format)
|
11
9
|
|
12
|
-
|
10
|
+
pathnames.each do |pathname|
|
11
|
+
Dir.chdir(pathname.dirname) do
|
12
|
+
generator.call(pathname.basename)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
class Brutal
|
6
|
+
# Accept an arbitrary number of arguments passed from the command-line.
|
7
|
+
class CommandLineArgumentsParser
|
8
|
+
DEFAULT_FORMAT = "ruby"
|
9
|
+
FILE_SUFFIX = "_brutal.yaml"
|
10
|
+
FILE_PATTERN = ::File.join("**", "*#{FILE_SUFFIX}")
|
11
|
+
CURRENT_EXECUTION_CONTEXT = "."
|
12
|
+
|
13
|
+
attr_reader :pathnames
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
@any_path = false
|
17
|
+
@pathnames = []
|
18
|
+
args.each { |arg| parse!(arg) }
|
19
|
+
parse!(CURRENT_EXECUTION_CONTEXT) unless any_path?
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
[format, pathnames]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def any_path?
|
29
|
+
@any_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def format
|
33
|
+
@format || DEFAULT_FORMAT
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse!(arg)
|
37
|
+
case arg
|
38
|
+
when "--format=ruby"
|
39
|
+
format!("Ruby")
|
40
|
+
when "--help"
|
41
|
+
help!
|
42
|
+
when "--version"
|
43
|
+
version!
|
44
|
+
else
|
45
|
+
pathname = ::Pathname.new(arg)
|
46
|
+
load!(pathname)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def format!(name)
|
51
|
+
raise ::ArgumentError, "Format already filled in." unless format.nil?
|
52
|
+
|
53
|
+
@format = name
|
54
|
+
end
|
55
|
+
|
56
|
+
def help!
|
57
|
+
puts help_command_output
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
def help_command_output
|
62
|
+
<<~TXT
|
63
|
+
Usage: #{$PROGRAM_NAME} [options] [files or directories]
|
64
|
+
|
65
|
+
--format=FORMAT Choose "ruby" (default).
|
66
|
+
--help Display this help.
|
67
|
+
--version Display the version.
|
68
|
+
TXT
|
69
|
+
end
|
70
|
+
|
71
|
+
def load!(pathname)
|
72
|
+
@any_path = true
|
73
|
+
|
74
|
+
if pathname.directory?
|
75
|
+
pathname.glob(FILE_PATTERN).each { |filename| load!(filename) }
|
76
|
+
elsif pathname.file?
|
77
|
+
if pathname.to_s.end_with?(FILE_SUFFIX)
|
78
|
+
@pathnames << pathname
|
79
|
+
else
|
80
|
+
warn "Skipping #{pathname} because not suffixed with #{FILE_SUFFIX}."
|
81
|
+
end
|
82
|
+
else
|
83
|
+
raise ::ArgumentError, "#{pathname} is neither a file nor a directory."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def version!
|
88
|
+
abort "Gem not found on the system. Unknown version." if not_loaded_spec?
|
89
|
+
|
90
|
+
puts loaded_spec.version
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
|
94
|
+
def loaded_spec
|
95
|
+
::Gem.loaded_specs["brutal"]
|
96
|
+
end
|
97
|
+
|
98
|
+
def not_loaded_spec?
|
99
|
+
loaded_spec.nil?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/brutal/configuration.rb
CHANGED
data/lib/brutal/file/read.rb
CHANGED
data/lib/brutal/file/write.rb
CHANGED
data/lib/brutal/file.rb
CHANGED
@@ -5,27 +5,13 @@
|
|
5
5
|
write
|
6
6
|
].each { |filename| require_relative(File.join("file", filename)) }
|
7
7
|
|
8
|
-
|
8
|
+
class Brutal
|
9
9
|
# Brutal::File
|
10
10
|
module File
|
11
|
-
DEFAULT_CONFIG_FILENAME = ".brutal.yml"
|
12
|
-
DEFAULT_GENERATED_FILENAME = "test.rb"
|
13
11
|
RUBY_EXTENSION = ".rb"
|
14
12
|
|
15
13
|
def self.generated_pathname(pathname)
|
16
|
-
filename = pathname.basename
|
17
|
-
return pathname.dirname + DEFAULT_GENERATED_FILENAME if default_config_filename?(filename)
|
18
|
-
|
19
14
|
pathname.sub_ext(RUBY_EXTENSION)
|
20
15
|
end
|
21
|
-
|
22
|
-
def self.override_protection(pathname)
|
23
|
-
abort "A #{pathname} file already exists!" if pathname.exist?
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.default_config_filename?(filename)
|
27
|
-
filename.to_s == DEFAULT_CONFIG_FILENAME
|
28
|
-
end
|
29
|
-
private_class_method :default_config_filename?
|
30
16
|
end
|
31
17
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Brutal
|
4
|
+
module Format
|
5
|
+
# Ruby format class
|
6
|
+
class Ruby
|
7
|
+
# Specifies templates to challenge evaluated subjects & get results.
|
8
|
+
attr_reader :actuals
|
9
|
+
|
10
|
+
# Specifies a list of variables to populate the subject's template.
|
11
|
+
attr_reader :contexts
|
12
|
+
|
13
|
+
# Specifies the code to execute before generating the test suite.
|
14
|
+
attr_reader :header
|
15
|
+
|
16
|
+
# Specifies the template of the code to be declined across contexts.
|
17
|
+
attr_reader :subject
|
18
|
+
|
19
|
+
# Initialize a new scaffold generator.
|
20
|
+
def initialize(header, subject, *actuals, **contexts)
|
21
|
+
warn("Empty subject!") if subject.empty?
|
22
|
+
warn("Empty actual values!") if actuals.empty?
|
23
|
+
warn("Empty contexts!") if contexts.empty?
|
24
|
+
|
25
|
+
eval(header) # rubocop:disable Security/Eval
|
26
|
+
|
27
|
+
@header = header
|
28
|
+
@subject = subject
|
29
|
+
@actuals = actuals
|
30
|
+
@contexts = contexts
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a Ruby string that can be evaluated.
|
34
|
+
def inspect(object)
|
35
|
+
return object.to_s unless object.is_a?(::String)
|
36
|
+
|
37
|
+
object.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a string representation.
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def to_s
|
44
|
+
ruby_lines.join(separator_ruby_code)
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes(*values)
|
48
|
+
context_names.each_with_index.inject({}) do |h, (name, i)|
|
49
|
+
h.merge(name.to_sym => inspect(values.fetch(i)))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def context_names
|
54
|
+
contexts.keys.sort
|
55
|
+
end
|
56
|
+
|
57
|
+
def contexts_values
|
58
|
+
context_names.map { |context_name| contexts.fetch(context_name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def combinations_values
|
62
|
+
Array(contexts_values[0]).product(*Array(contexts_values[1..]))
|
63
|
+
end
|
64
|
+
|
65
|
+
def ruby_lines
|
66
|
+
[header_ruby_code] + actual_ruby_codes
|
67
|
+
end
|
68
|
+
|
69
|
+
def actual_ruby_codes
|
70
|
+
combinations_values.map do |values|
|
71
|
+
actual_str = format(inspect(subject), **attributes(*values))
|
72
|
+
string = actual_ruby_code(actual_str)
|
73
|
+
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
74
|
+
|
75
|
+
actuals.each do |actual_value|
|
76
|
+
result_str = format(actual_value, subject: "actual")
|
77
|
+
string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
78
|
+
end
|
79
|
+
|
80
|
+
string
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def actual_ruby_code(actual_str)
|
85
|
+
<<~RUBY_CODE
|
86
|
+
actual = begin
|
87
|
+
#{actual_str.gsub(/^/, ' ')}
|
88
|
+
end
|
89
|
+
|
90
|
+
RUBY_CODE
|
91
|
+
end
|
92
|
+
|
93
|
+
def header_ruby_code
|
94
|
+
<<~RUBY_CODE
|
95
|
+
#{header.chomp}
|
96
|
+
RUBY_CODE
|
97
|
+
end
|
98
|
+
|
99
|
+
def separator_ruby_code
|
100
|
+
<<~RUBY_CODE
|
101
|
+
|
102
|
+
#{thematic_break_ruby_code}
|
103
|
+
RUBY_CODE
|
104
|
+
end
|
105
|
+
|
106
|
+
def thematic_break_ruby_code
|
107
|
+
<<~RUBY_CODE
|
108
|
+
# #{'-' * 78}
|
109
|
+
RUBY_CODE
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
%w[
|
4
|
+
ruby
|
5
|
+
].each { |filename| require_relative(File.join("format", filename)) }
|
6
|
+
|
7
|
+
class Brutal
|
8
|
+
# Brutal::Format
|
9
|
+
module Format
|
10
|
+
SUPPORT = {
|
11
|
+
"ruby" => Ruby
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
DEFAULT = SUPPORT.keys.sort.fetch(-1)
|
15
|
+
end
|
16
|
+
end
|
data/lib/brutal/yaml.rb
CHANGED
@@ -2,15 +2,10 @@
|
|
2
2
|
|
3
3
|
require "yaml"
|
4
4
|
|
5
|
-
|
5
|
+
class Brutal
|
6
6
|
# Brutal::Yaml
|
7
|
-
#
|
8
|
-
# @since 1.1.0
|
9
7
|
module Yaml
|
10
|
-
|
11
|
-
.yaml
|
12
|
-
.yml
|
13
|
-
].freeze
|
8
|
+
FILENAME_EXTENSION = ".yaml"
|
14
9
|
|
15
10
|
def self.parse(yaml)
|
16
11
|
::YAML.safe_load(yaml, symbolize_names: false)
|
@@ -18,7 +13,7 @@ module Brutal
|
|
18
13
|
|
19
14
|
def self.parse?(pathname)
|
20
15
|
filename_extension = pathname.extname
|
21
|
-
|
16
|
+
filename_extension.eql?(FILENAME_EXTENSION)
|
22
17
|
end
|
23
18
|
end
|
24
19
|
end
|
data/lib/brutal.rb
CHANGED
@@ -3,41 +3,48 @@
|
|
3
3
|
%w[
|
4
4
|
configuration
|
5
5
|
file
|
6
|
-
|
6
|
+
format
|
7
7
|
yaml
|
8
8
|
].each { |filename| require_relative(File.join("brutal", filename)) }
|
9
9
|
|
10
10
|
# The Brutal namespace.
|
11
|
-
|
12
|
-
|
11
|
+
class Brutal
|
12
|
+
attr_reader :format
|
13
|
+
|
14
|
+
def initialize(format: Format::DEFAULT)
|
15
|
+
@format = String(format)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(pathname)
|
13
19
|
hash = parse(pathname)
|
14
20
|
conf = Configuration.load(hash)
|
21
|
+
code = scaffold(conf)
|
22
|
+
write(pathname, code)
|
23
|
+
end
|
15
24
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
25
|
+
def scaffold(conf)
|
26
|
+
engine = Format::SUPPORT.fetch(format) do
|
27
|
+
raise ::NotImplementedError, "#{format.inspect} format is not supported."
|
28
|
+
end
|
20
29
|
|
21
|
-
|
30
|
+
engine.new(conf.header, conf.subject, *conf.actuals, **conf.contexts)
|
22
31
|
end
|
23
32
|
|
24
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse(pathname)
|
25
36
|
return Yaml.parse(read(pathname)) if Yaml.parse?(pathname)
|
26
37
|
|
27
38
|
raise ::ArgumentError, "Unrecognized extension. " \
|
28
39
|
"Impossible to parse #{pathname.inspect}."
|
29
40
|
end
|
30
|
-
private_class_method :parse
|
31
41
|
|
32
|
-
def
|
42
|
+
def read(pathname)
|
33
43
|
File::Read.new(pathname).call
|
34
44
|
end
|
35
|
-
private_class_method :read
|
36
45
|
|
37
|
-
def
|
46
|
+
def write(pathname, ruby)
|
38
47
|
new_pathname = File.generated_pathname(pathname)
|
39
|
-
File.override_protection(new_pathname) unless force
|
40
48
|
File::Write.new(new_pathname).call(ruby)
|
41
49
|
end
|
42
|
-
private_class_method :write
|
43
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brutal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -147,11 +147,13 @@ files:
|
|
147
147
|
- README.md
|
148
148
|
- bin/brutal
|
149
149
|
- lib/brutal.rb
|
150
|
+
- lib/brutal/command_line_arguments_parser.rb
|
150
151
|
- lib/brutal/configuration.rb
|
151
152
|
- lib/brutal/file.rb
|
152
153
|
- lib/brutal/file/read.rb
|
153
154
|
- lib/brutal/file/write.rb
|
154
|
-
- lib/brutal/
|
155
|
+
- lib/brutal/format.rb
|
156
|
+
- lib/brutal/format/ruby.rb
|
155
157
|
- lib/brutal/yaml.rb
|
156
158
|
homepage: https://github.com/fixrb/brutal
|
157
159
|
licenses:
|
@@ -169,9 +171,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
169
171
|
version: 2.7.0
|
170
172
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
173
|
requirements:
|
172
|
-
- - "
|
174
|
+
- - ">"
|
173
175
|
- !ruby/object:Gem::Version
|
174
|
-
version:
|
176
|
+
version: 1.3.1
|
175
177
|
requirements: []
|
176
178
|
rubygems_version: 3.1.6
|
177
179
|
signing_key:
|
data/lib/brutal/scaffold.rb
DELETED
@@ -1,113 +0,0 @@
|
|
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
|
-
def to_s
|
45
|
-
ruby_lines.join(separator_ruby_code)
|
46
|
-
end
|
47
|
-
|
48
|
-
def attributes(*values)
|
49
|
-
context_names.each_with_index.inject({}) do |h, (name, i)|
|
50
|
-
h.merge(name.to_sym => inspect(values.fetch(i)))
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def context_names
|
55
|
-
contexts.keys.sort
|
56
|
-
end
|
57
|
-
|
58
|
-
def contexts_values
|
59
|
-
context_names.map { |context_name| contexts.fetch(context_name) }
|
60
|
-
end
|
61
|
-
|
62
|
-
def combinations_values
|
63
|
-
Array(contexts_values[0]).product(*Array(contexts_values[1..]))
|
64
|
-
end
|
65
|
-
|
66
|
-
def ruby_lines
|
67
|
-
[header_ruby_code] + actual_ruby_codes
|
68
|
-
end
|
69
|
-
|
70
|
-
def actual_ruby_codes
|
71
|
-
combinations_values.map do |values|
|
72
|
-
actual_str = format(inspect(subject), **attributes(*values))
|
73
|
-
string = actual_ruby_code(actual_str)
|
74
|
-
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
75
|
-
|
76
|
-
actuals.each do |actual_value|
|
77
|
-
result_str = format(actual_value, subject: "actual")
|
78
|
-
string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
79
|
-
end
|
80
|
-
|
81
|
-
string
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def actual_ruby_code(actual_str)
|
86
|
-
<<~RUBY_CODE
|
87
|
-
actual = begin
|
88
|
-
#{actual_str.gsub(/^/, ' ')}
|
89
|
-
end
|
90
|
-
|
91
|
-
RUBY_CODE
|
92
|
-
end
|
93
|
-
|
94
|
-
def header_ruby_code
|
95
|
-
<<~RUBY_CODE
|
96
|
-
#{header.chomp}
|
97
|
-
RUBY_CODE
|
98
|
-
end
|
99
|
-
|
100
|
-
def separator_ruby_code
|
101
|
-
<<~RUBY_CODE
|
102
|
-
|
103
|
-
#{thematic_break_ruby_code}
|
104
|
-
RUBY_CODE
|
105
|
-
end
|
106
|
-
|
107
|
-
def thematic_break_ruby_code
|
108
|
-
<<~RUBY_CODE
|
109
|
-
# #{'-' * 78}
|
110
|
-
RUBY_CODE
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|