brutal 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a7eb1b01e30dda0f5b204eef32ac2ec59c19384b37500f40587a1a522fce6db
4
- data.tar.gz: be19e4c8f4741658ef2aea98891b8bf040a250361fe48fee5219b02cc297b10f
3
+ metadata.gz: 31ea2d6a9b38faaa03aaed25dfc8b18158a8d243b9aed42e0f4a3a488c8386a3
4
+ data.tar.gz: f73e4a2798de7a55deb006898919498a9d6344fcd3ec94728a379bf723370ae2
5
5
  SHA512:
6
- metadata.gz: 565a5d6628dd7f3904f97343da33539eaddf97c6fa020d2c14736ed84a146c3fe3d0aa7eb6fdfc73a94a3238be3bba40d892f07281d231918022b2c96fb95ce6
7
- data.tar.gz: 245f56b40a04d626c653e679b3634a5e1f3805a4f356c6f009759cb9e350e675b89e17e699e430fac5ffcb596e83c19190b929b4086b1da42254f2ffdbc7ea60
6
+ metadata.gz: f5a9b52b8608bdf327bb14ad782cde378901285a3d9de0b28d273184480586f868da0b10bf32ac47980bfbc0b637b6bc058de2c42bdb600b4add7bb1865207e0
7
+ data.tar.gz: 302e2bf37546bc813caa05eedb38f2ad496123e6ba37b186d449ea2e5782945ef5d652d8cdc05a25c08045a472144ad6efa30f4316c23f5e3ca9ecdbdd8c8818
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ # The MIT License
2
2
 
3
- Copyright (c) 2020-2021 Cyril Kato
3
+ Copyright (c) 2020-2022 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,10 +1,10 @@
1
1
  # Brutal 💎🔨
2
2
 
3
- [![RuboCop Status](https://github.com/fixrb/brutal/workflows/RuboCop/badge.svg)][workflow_rubocop]
4
- [![Build Status](https://api.travis-ci.org/fixrb/brutal.svg?branch=main)][travis]
5
- [![Gem Version](https://badge.fury.io/rb/brutal.svg)][gem]
6
- [![Inline docs](https://inch-ci.org/github/fixrb/brutal.svg?branch=main)][inchpages]
7
- [![Documentation](http://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ [![Version](https://img.shields.io/github/v/tag/fixrb/brutal?label=Version&logo=github)](https://github.com/fixrb/brutal/tags)
4
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/brutal/main)
5
+ [![Ruby](https://github.com/fixrb/brutal/workflows/Ruby/badge.svg?branch=main)](https://github.com/fixrb/brutal/actions?query=workflow%3Aruby+branch%3Amain)
6
+ [![RuboCop](https://github.com/fixrb/brutal/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/brutal/actions?query=workflow%3Arubocop+branch%3Amain)
7
+ [![License](https://img.shields.io/github/license/fixrb/brutal?label=License&logo=github)](https://github.com/fixrb/brutal/raw/main/LICENSE.md)
8
8
 
9
9
  > A _code-first_ approach to automate the writing of unit tests.
10
10
 
@@ -12,26 +12,26 @@
12
12
 
13
13
  [![I Hate Tests](https://github.com/fixrb/brutal/raw/main/img/rubyhack-2019-ruby3-what-s-missing-by-yukihiro-matsumoto.jpg)](https://www.youtube.com/embed/cmOt9HhszCI?start=1732&end=1736 "I don't like tests. It's not DRY.")
14
14
 
15
- > I don't like tests. It's not DRY.<br/>
15
+ > I don't like tests. It's not DRY.
16
16
  > -- [Matz](https://github.com/matz)
17
17
 
18
18
  ## Overview
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
+ Let __Brutal__ craft for you in no time a (potentially huge) framework-less vanilla Ruby file describing the actual behavior of your code across as many context combinations as necessary.
21
21
 
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.
22
+ By delegating to __Brutal__ the repetitive (and redundant) task of writing tests, you will be able to focus on your core business: the code itself.
23
+
24
+ ![Brutal-Driven Development](https://github.com/fixrb/brutal/raw/main/img/brutal-driven-development.jpg)
23
25
 
24
26
  ## Warning
25
27
 
26
- __Brutal__ development process does not prevent from bugs.
28
+ The _Brutal-Driven Development_ process does not prevent bugs from appearing in the code.
29
+
30
+ A generated test suite acts as a _picture of the code's behavior_. Therefore, if the code is wrong, the picture of the code's behavior will also be wrong.
27
31
 
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.
32
+ The mere fact that all expectations are true does not mean that the code behaves as it should.
31
33
 
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_.
34
+ It is therefore the responsibility of the developer to analyze the generated behavioral pictures to ensure that the code reacts as it is supposed to according to the contexts in which it is evaluated.
35
35
 
36
36
  ## Installation
37
37
 
@@ -43,11 +43,15 @@ gem "brutal"
43
43
 
44
44
  And then execute:
45
45
 
46
- $ bundle install
46
+ ```sh
47
+ bundle install
48
+ ```
47
49
 
48
50
  Or install it yourself as:
49
51
 
50
- $ gem install brutal
52
+ ```sh
53
+ gem install brutal
54
+ ```
51
55
 
52
56
  ## Quick Start
53
57
 
@@ -55,63 +59,40 @@ Just type `brutal` in a Ruby project's folder and watch the magic happen.
55
59
 
56
60
  ## Usage
57
61
 
58
- The `brutal.yml` file is a manifest you can use to define your __Brutal__ meta-spec.
59
- It has 4 top-level sections:
62
+ __Brutal__ needs a configuration file to know how to write your tests.
63
+ Currently, only the YAML format is supported.
64
+ This file is composed of 4 top-level sections:
60
65
 
61
66
  * `header` - Specifies the code to execute before generating the test suite.
62
67
  * `subject` - Specifies the template of the code to be declined across contexts.
63
68
  * `contexts` - Specifies a list of variables to populate the subject's template.
64
69
  * `actuals` - Specifies templates to challenge evaluated subjects & get results.
65
70
 
66
- ### Getting started
67
-
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:
71
+ This file is by default called `.brutal.yml`, but it would be possible to name it differently by passing it as an argument to the brutal command such as:
70
72
 
71
- ```yaml
72
- ---
73
- subject: |
74
- "Hello " + "%{string}"
75
-
76
- contexts:
77
- string:
78
- - Alice
79
- - Bob
80
-
81
- actuals:
82
- - "%{subject}.to_s"
83
- - "%{subject}.length"
73
+ ```sh
74
+ brutal test_hello_world.yml
84
75
  ```
85
76
 
86
- 2. Run the `brutal` command from the same directory.
87
-
88
- 3. Read the generated `test.rb` file in the same directory:
89
-
90
- ```ruby
91
- # Brutal test suite
92
-
93
- # ------------------------------------------------------------------------------
77
+ This would create a `test_hello_world.rb` file containing the test suite.
94
78
 
95
- actual = begin
96
- "Hello " + "Alice"
97
- end
79
+ To avoid accidentally overwriting a file, the `--no-force` option can be used:
98
80
 
99
- raise if actual.to_s != "Hello Alice"
100
- raise if actual.length != 11
81
+ ```sh
82
+ brutal test_hello_world.yml --no-force
83
+ ```
101
84
 
102
- # ------------------------------------------------------------------------------
85
+ > A test_hello_world.rb file already exists!
103
86
 
104
- actual = begin
105
- "Hello " + "Bob"
106
- end
87
+ ### Getting started
107
88
 
108
- raise if actual.to_s != "Hello Bob"
109
- raise if actual.length != 9
110
- ```
89
+ 1. Create a `.brutal.yml` file in your application's root directory. For example: <https://github.com/fixrb/brutal/blob/v1.4.0/examples/hello_world_v1/.brutal.yml>
90
+ 2. Run the `brutal` command from the same directory.
91
+ 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>
111
92
 
112
93
  ### More examples
113
94
 
114
- https://github.com/fixrb/brutal/raw/main/examples/
95
+ <https://github.com/fixrb/brutal/blob/v1.4.0/examples/>
115
96
 
116
97
  ## Rake integration example
117
98
 
@@ -123,6 +104,10 @@ Rake::TestTask.new do |t|
123
104
  end
124
105
  ```
125
106
 
107
+ ## Test suite
108
+
109
+ __Brutal__'s test set is brutally self-generated here: [./test.rb](https://github.com/fixrb/brutal/blob/main/test.rb)
110
+
126
111
  ## Contact
127
112
 
128
113
  * Source code: https://github.com/fixrb/brutal
@@ -133,19 +118,11 @@ __Brutal__ follows [Semantic Versioning 2.0](https://semver.org/).
133
118
 
134
119
  ## License
135
120
 
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).
121
+ The [gem](https://rubygems.org/gems/brutal) is available as open source under the terms of the [MIT License](https://github.com/fixrb/brutal/raw/main/LICENSE.md).
137
122
 
138
123
  ***
139
124
 
140
- <p>
141
- This project is sponsored by:<br />
142
- <a href="https://sashite.com/"><img
143
- src="https://github.com/fixrb/brutal/raw/main/img/sashite.png"
144
- alt="Sashite" /></a>
145
- </p>
146
-
147
- [workflow_rubocop]: https://github.com/fixrb/brutal/actions?query=workflow%3ARuboCop
148
- [gem]: https://rubygems.org/gems/brutal
149
- [travis]: https://travis-ci.org/fixrb/brutal
150
- [inchpages]: https://inch-ci.org/github/fixrb/brutal
151
- [rubydoc]: https://rubydoc.info/gems/brutal/frames
125
+ This project is sponsored by [Sashité](https://github.com/sashite/):
126
+
127
+ ![Sashité logo](https://github.com/fixrb/brutal/raw/main/img/sponsor/dark/en/sashite.png#gh-dark-mode-only "Sashité")
128
+ ![Sashité logo](https://github.com/fixrb/brutal/raw/main/img/sponsor/light/en/sashite.png#gh-light-mode-only "Sashité")
data/bin/brutal CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  require_relative File.join("..", "lib", "brutal")
5
5
 
6
- Brutal.generate!
6
+ Brutal.generate! ARGV.fetch(0, Brutal::File::DEFAULT_CONFIG_FILENAME), force: ARGV.none?("--no-force")
@@ -5,9 +5,14 @@ module Brutal
5
5
  #
6
6
  # @since 1.0.0
7
7
  class Configuration
8
+ ACTUALS_KEY = "actuals"
9
+ CONTEXTS_KEY = "contexts"
10
+ HEADER_KEY = "header"
11
+ SUBJECT_KEY = "subject"
12
+
8
13
  DEFAULT_ACTUALS = [].freeze
9
14
  DEFAULT_CONTEXTS = {}.freeze
10
- DEFAULT_HEAD = "# Brutal test suite"
15
+ DEFAULT_HEADER = "# Brutal test suite"
11
16
  DEFAULT_SUBJECT = ""
12
17
 
13
18
  # Load the configuration parameters.
@@ -15,10 +20,10 @@ module Brutal
15
20
  # @param params [Hash] Receive the 4 top-level section parameters.
16
21
  def self.load(params)
17
22
  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)
23
+ actuals: params.fetch(ACTUALS_KEY, DEFAULT_ACTUALS),
24
+ contexts: params.fetch(CONTEXTS_KEY, DEFAULT_CONTEXTS),
25
+ header: params.fetch(HEADER_KEY, DEFAULT_HEADER),
26
+ subject: params.fetch(SUBJECT_KEY, DEFAULT_SUBJECT)
22
27
  )
23
28
  end
24
29
 
@@ -1,19 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brutal
4
- # Brutal::File
5
- #
6
- # @since 1.1.0
7
4
  module File
8
5
  # Brutal::File::Read
9
6
  #
10
7
  # @since 1.1.0
11
8
  class Read
12
- NAME = ".brutal.yml"
13
-
14
9
  attr_reader :name
15
10
 
16
- def initialize(name = NAME)
11
+ def initialize(name)
17
12
  @name = name
18
13
  end
19
14
 
@@ -1,19 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brutal
4
- # Brutal::File
5
- #
6
- # @since 1.1.0
7
4
  module File
8
5
  # Brutal::File::Write
9
6
  #
10
7
  # @since 1.1.0
11
8
  class Write
12
- NAME = "test.rb"
13
-
14
9
  attr_reader :name
15
10
 
16
- def initialize(name = NAME)
11
+ def initialize(name)
17
12
  @name = name
18
13
  end
19
14
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[
4
+ read
5
+ write
6
+ ].each { |filename| require_relative(File.join("file", filename)) }
7
+
8
+ module Brutal
9
+ # Brutal::File
10
+ module File
11
+ DEFAULT_CONFIG_FILENAME = ".brutal.yml"
12
+ DEFAULT_GENERATED_FILENAME = "test.rb"
13
+
14
+ def self.generated_pathname(pathname)
15
+ filename = pathname.split(separator).fetch(-1)
16
+
17
+ if filename == DEFAULT_CONFIG_FILENAME
18
+ directory_parts = pathname.split(separator)[..-2]
19
+ path_parts = directory_parts + [DEFAULT_GENERATED_FILENAME]
20
+ return path_parts.join(separator)
21
+ end
22
+
23
+ pathname.gsub(/.[^.]+\z/, ".rb")
24
+ end
25
+
26
+ def self.override_protection(pathname)
27
+ return true unless ::File.exist?(pathname)
28
+
29
+ abort "A #{pathname} file already exists!"
30
+ end
31
+
32
+ def self.separator
33
+ ::File::SEPARATOR
34
+ end
35
+ private_class_method :separator
36
+ end
37
+ end
@@ -41,23 +41,36 @@ module Brutal
41
41
  # Return a string representation.
42
42
  #
43
43
  # @return [String]
44
- #
45
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
44
  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
45
+ ruby_lines.join(separator_ruby_code)
46
+ end
51
47
 
52
- actual_str = format(inspect(subject), **attributes)
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
53
 
54
- string = <<~CODE
55
- actual = begin
56
- #{actual_str.gsub(/^/, ' ')}
57
- end
54
+ def context_names
55
+ contexts.keys.sort
56
+ end
58
57
 
59
- CODE
58
+ def contexts_values
59
+ context_names.map { |context_name| contexts.fetch(context_name) }
60
+ end
60
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)
61
74
  actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
62
75
 
63
76
  actuals.each do |actual_value|
@@ -66,26 +79,35 @@ module Brutal
66
79
  end
67
80
 
68
81
  string
69
- end.join(blank_line)
82
+ end
70
83
  end
71
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
72
84
 
73
- def blank_line
74
- "\n" \
75
- "# #{'-' * 78}\n" \
76
- "\n"
85
+ def actual_ruby_code(actual_str)
86
+ <<~RUBY_CODE
87
+ actual = begin
88
+ #{actual_str.gsub(/^/, ' ')}
89
+ end
90
+
91
+ RUBY_CODE
77
92
  end
78
93
 
79
- def context_names
80
- contexts.keys.sort
94
+ def header_ruby_code
95
+ <<~RUBY_CODE
96
+ #{header.chomp}
97
+ RUBY_CODE
81
98
  end
82
99
 
83
- def contexts_values
84
- context_names.map { |context_name| contexts.fetch(context_name) }
100
+ def separator_ruby_code
101
+ <<~RUBY_CODE
102
+
103
+ #{thematic_break_ruby_code}
104
+ RUBY_CODE
85
105
  end
86
106
 
87
- def combinations_values
88
- Array(contexts_values[0]).product(*Array(contexts_values[1..]))
107
+ def thematic_break_ruby_code
108
+ <<~RUBY_CODE
109
+ # #{'-' * 78}
110
+ RUBY_CODE
89
111
  end
90
112
  end
91
113
  end
data/lib/brutal/yaml.rb CHANGED
@@ -7,8 +7,18 @@ module Brutal
7
7
  #
8
8
  # @since 1.1.0
9
9
  module Yaml
10
+ FILENAME_EXTENSIONS = %w[
11
+ yaml
12
+ yml
13
+ ].freeze
14
+
10
15
  def self.parse(yaml)
11
16
  ::YAML.safe_load(yaml, symbolize_names: false)
12
17
  end
18
+
19
+ def self.parse?(pathname)
20
+ filename_extension = pathname.split(".")[1..][-1]
21
+ FILENAME_EXTENSIONS.include?(filename_extension)
22
+ end
13
23
  end
14
24
  end
data/lib/brutal.rb CHANGED
@@ -2,23 +2,42 @@
2
2
 
3
3
  %w[
4
4
  configuration
5
- file/read
6
- file/write
5
+ file
7
6
  scaffold
8
7
  yaml
9
- ].each { |file_name| require_relative(File.join("brutal", file_name)) }
8
+ ].each { |filename| require_relative(File.join("brutal", filename)) }
10
9
 
11
10
  # The Brutal namespace.
12
11
  module Brutal
13
- def self.generate!
14
- yaml = File::Read.new.call
15
- hash = Yaml.parse(yaml)
12
+ def self.generate!(pathname, force: true)
13
+ hash = parse(pathname)
16
14
  conf = Configuration.load(hash)
15
+
17
16
  ruby = Scaffold.new(conf.header,
18
17
  conf.subject,
19
18
  *conf.actuals,
20
19
  **conf.contexts)
21
20
 
22
- File::Write.new.call(ruby)
21
+ write(pathname, ruby, force: force)
22
+ end
23
+
24
+ def self.parse(pathname)
25
+ return Yaml.parse(read(pathname)) if Yaml.parse?(pathname)
26
+
27
+ raise ::ArgumentError, "Unrecognized extension. " \
28
+ "Impossible to parse #{pathname.inspect}."
29
+ end
30
+ private_class_method :parse
31
+
32
+ def self.read(pathname)
33
+ File::Read.new(pathname).call
34
+ end
35
+ private_class_method :read
36
+
37
+ def self.write(pathname, ruby, force:)
38
+ new_pathname = File.generated_pathname(pathname)
39
+ File.override_protection(new_pathname) unless force
40
+ File::Write.new(new_pathname).call(ruby)
23
41
  end
42
+ private_class_method :write
24
43
  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.2.0
4
+ version: 1.4.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: 2021-05-12 00:00:00.000000000 Z
11
+ date: 2022-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-gitlab-security
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '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'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rubocop-md
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +148,7 @@ files:
134
148
  - bin/brutal
135
149
  - lib/brutal.rb
136
150
  - lib/brutal/configuration.rb
151
+ - lib/brutal/file.rb
137
152
  - lib/brutal/file/read.rb
138
153
  - lib/brutal/file/write.rb
139
154
  - lib/brutal/scaffold.rb
@@ -141,7 +156,8 @@ files:
141
156
  homepage: https://github.com/fixrb/brutal
142
157
  licenses:
143
158
  - MIT
144
- metadata: {}
159
+ metadata:
160
+ rubygems_mfa_required: 'true'
145
161
  post_install_message:
146
162
  rdoc_options: []
147
163
  require_paths:
@@ -157,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
173
  - !ruby/object:Gem::Version
158
174
  version: '0'
159
175
  requirements: []
160
- rubygems_version: 3.1.4
176
+ rubygems_version: 3.1.6
161
177
  signing_key:
162
178
  specification_version: 4
163
179
  summary: A code-first approach to automate the writing of unit tests.