rast 0.15.1 → 0.18.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/CHANGELOG.md +12 -0
- data/Getting-Started.md +139 -0
- data/examples/logic_checker.rb +8 -0
- data/examples/lohika.rb +27 -0
- data/examples/positive.rb +0 -2
- data/examples/positive2.rb +10 -0
- data/examples/quiz_module.rb +0 -44
- data/lib/rast/converters/str_converter.rb +1 -1
- data/lib/rast/parameter_generator.rb +31 -17
- data/lib/rast/rast_spec.rb +8 -10
- data/lib/rast/rules/logic_helper.rb +17 -13
- data/lib/rast/rules/rule.rb +5 -30
- data/lib/rast/rules/rule_evaluator.rb +28 -36
- data/lib/rast/rules/rule_validator.rb +8 -16
- data/lib/rast/spec_dsl.rb +26 -27
- data/lib/rast.rb +4 -4
- data/lib/template_spec.yml +6 -8
- data/rast.gemspec +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77e02b108231f3d36dd5e490e9a0ab9cf1334ebb53ae6ffce93cbec2c124423b
|
4
|
+
data.tar.gz: a738c7603f64ec75eff5a13c87fa472d147c1262efb0340d2b500d76fa590826
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88fc71ee6789773bd21b743c9e8bafbabc07654fbc2494f65e7b855930f32f686222ffe0ada7ecafeab1a828b9f45f8a9d94debf5ca7c87603edfbf39e10e4b3
|
7
|
+
data.tar.gz: '01409ef86dc48e884d0a4afd8cfa6113c054a4bd70aa19e8d5ab34d0fac5f50119f4e26924634dea6c8e710ba131fc1cfaf79ae07a884a40a48e8e6be2a84346'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
- 0.18.0 - [feature] xspec to skip a test
|
4
|
+
- [feature] Added include rule to isolate scenarios.
|
5
|
+
- [feature] Asterisk can be used as token character.
|
6
|
+
- [bug] Duplicate outcome wasn't detected when there's default config.
|
7
|
+
- [enhancement] Early return for 'and' and 'or' operators.
|
8
|
+
|
9
|
+
- 0.17.0 - Allow default outcome for unmatched scenarios.
|
10
|
+
- 0.16.0 - Allow isolation of scenarios via include clause.
|
11
|
+
- 0.15.4 - Fix bug on logic checker for not operation.
|
12
|
+
- 0.15.3 - Fix use of boolean as key to the config.
|
13
|
+
- Fix allow factory to be called inside both execute and prepare block.
|
14
|
+
- 0.15.2 - Fix handling of nil token, and big integers.
|
3
15
|
- 0.15.1 - Fix converters when variable is multi typed.
|
4
16
|
- 0.14.0 - Introduced an else config as substitute for pair.
|
5
17
|
- 0.13.0 - Make pair in config optional for boolean result.
|
data/Getting-Started.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
## Tutorials
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
### Writing a simple test.
|
10
|
+
|
11
|
+
Suppose we want to create a class that checks if a number is a positive number or not.
|
12
|
+
|
13
|
+
#### Step 1: Defining how to exercise the system under test.
|
14
|
+
|
15
|
+
Start by creating a spec file inside your `spec` folder, let's call it `positive_spec.rb`
|
16
|
+
|
17
|
+
- spec
|
18
|
+
- positive_spec.rb
|
19
|
+
|
20
|
+
|
21
|
+
`positive_spec.rb` would look like this:
|
22
|
+
|
23
|
+
```
|
24
|
+
require 'rast'
|
25
|
+
|
26
|
+
rast Positive do
|
27
|
+
spec 'Is Positive Example' do
|
28
|
+
execute { |number| subject.positive?(number) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
- On line: 1, the library is required:
|
34
|
+
|
35
|
+
```
|
36
|
+
require 'rast'
|
37
|
+
```
|
38
|
+
|
39
|
+
- On line: 3, invoke the DSL:
|
40
|
+
|
41
|
+
```
|
42
|
+
rast Positive do
|
43
|
+
```
|
44
|
+
`Positive` is the fully qualified name of the class or the module to be tested.
|
45
|
+
|
46
|
+
- On line: 4, define the first spec:
|
47
|
+
|
48
|
+
```
|
49
|
+
spec 'Is Positive Example' do
|
50
|
+
```
|
51
|
+
|
52
|
+
`'Is Positive Example'` is a descriptive identifier for the test. It can be the name of the method like `#positive?`.
|
53
|
+
|
54
|
+
- On line: 5, define the `execute` block..
|
55
|
+
|
56
|
+
```
|
57
|
+
execute { |number| subject.positive?(number) }
|
58
|
+
```
|
59
|
+
|
60
|
+
There is a lot going on here.<br>
|
61
|
+
- Inside the block, we define how our subject will be invoked. The `positive?` method is invoked on the `subject`
|
62
|
+
accepting a parameter.
|
63
|
+
- Block parameter `number` will contain the test scenario we want to execute. More on this later. Just know that whichever values are passed here, the `subject` has to be able to handle it correctly.
|
64
|
+
- `subject` is an instance of the module or class that we defined on line: 3.
|
65
|
+
- And lastly, `execute` block expects a return value that will be verified when the spec is run.
|
66
|
+
|
67
|
+
#### Step 2: Defining outcomes and the variables that affects the test.
|
68
|
+
|
69
|
+
For the sake of simplicity, we will define the variables and outcomes in a separate yaml file, mainly because the next
|
70
|
+
steps are purely configurations. Using yaml is completely optional.
|
71
|
+
|
72
|
+
Create a folder `rast` in the same level as the spec file:
|
73
|
+
|
74
|
+
- spec
|
75
|
+
- rast
|
76
|
+
|
77
|
+
Create a yaml file with the same name as the spec, but with `.yml` extension.
|
78
|
+
|
79
|
+
`spec/rast/positive_spec.yml` would contain:
|
80
|
+
|
81
|
+
```
|
82
|
+
specs:
|
83
|
+
Is Positive Exaple:
|
84
|
+
variables: {number: [-1, 0, 1]}
|
85
|
+
outcomes: {true: 1}
|
86
|
+
```
|
87
|
+
|
88
|
+
- On line: 1 is the root configuration element `specs` that will contain one or more specs.
|
89
|
+
- On line: 2 `Is Positive Example` is the spec identifier, this must match what we've defined on `positive_spec.rb:4`
|
90
|
+
- On line: 3, The variables that affect the SUT are defined, in this case there is only one variable called `number`,
|
91
|
+
which has 3 different possibilities `-1`, `0`, and `1`.
|
92
|
+
- On line: 4, This is where outcome to rule mapping is defined.
|
93
|
+
|
94
|
+
### The implementation file
|
95
|
+
|
96
|
+
positive.rb will have:
|
97
|
+
|
98
|
+
```
|
99
|
+
class Positive
|
100
|
+
def positive?(number)
|
101
|
+
number > 0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
## How To's
|
107
|
+
|
108
|
+
### Use token subscript when there's a variable token clash.
|
109
|
+
|
110
|
+
In cases where multiple variables, `left` and `right`, has the same set of tokens `false` and `true`:
|
111
|
+
|
112
|
+
```
|
113
|
+
variables:
|
114
|
+
left: [false, true]
|
115
|
+
right: [false, true]
|
116
|
+
```
|
117
|
+
|
118
|
+
We need a way to uniquely identify the tokens in the `outcomes` configuration. We can do so by using a subscript in the format: `token[n]`
|
119
|
+
For Example:
|
120
|
+
|
121
|
+
```
|
122
|
+
outcomes: {true: 'true[0] & true[1]'}
|
123
|
+
```
|
124
|
+
|
125
|
+
The subscript `0` would refer to the `true` token in the `left` variable and subscript `1` would refer to the `true`
|
126
|
+
token in the `right` variable.
|
127
|
+
|
128
|
+
|
129
|
+
## References
|
130
|
+
|
131
|
+
### Outcomes
|
132
|
+
|
133
|
+
|
134
|
+
## Current Limitations.
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
|
data/examples/logic_checker.rb
CHANGED
@@ -20,4 +20,12 @@ class LogicChecker
|
|
20
20
|
def or(argument1, argument2)
|
21
21
|
argument1 || argument2
|
22
22
|
end
|
23
|
+
|
24
|
+
# Perform logical XOR operation on two arguments.
|
25
|
+
#
|
26
|
+
# @param argument1 first argument of Boolean type.
|
27
|
+
# @param argument2 second argument of Boolean type.
|
28
|
+
def xor(argument1, argument2)
|
29
|
+
argument1 && !argument2 || !argument1 && argument2
|
30
|
+
end
|
23
31
|
end
|
data/examples/lohika.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# LogicChecker ported from Java
|
4
|
+
#
|
5
|
+
# @author Royce Remulla
|
6
|
+
#
|
7
|
+
class Lohika
|
8
|
+
# Perform logical AND operation on two arguments.
|
9
|
+
#
|
10
|
+
# @param argument1 first argument of Boolean type.
|
11
|
+
# @param argument2 second argument of Boolean type.
|
12
|
+
def at(argument1, argument2)
|
13
|
+
return :oo if argument1 == 'oo' && argument2 == 'oo'
|
14
|
+
|
15
|
+
:hindi
|
16
|
+
end
|
17
|
+
|
18
|
+
# Perform logical OR operation on two arguments.
|
19
|
+
#
|
20
|
+
# @param argument1 first argument of Boolean type.
|
21
|
+
# @param argument2 second argument of Boolean type.
|
22
|
+
def o(argument1, argument2)
|
23
|
+
return :oo if argument1 == 'oo' || argument2 == 'oo'
|
24
|
+
|
25
|
+
:hindi
|
26
|
+
end
|
27
|
+
end
|
data/examples/positive.rb
CHANGED
data/examples/quiz_module.rb
CHANGED
@@ -16,21 +16,6 @@ module QuizModule
|
|
16
16
|
Regexp::IGNORECASE
|
17
17
|
)
|
18
18
|
|
19
|
-
def quiz?(front = nil, back = nil)
|
20
|
-
initialized = [true, false].include? @quiz
|
21
|
-
@quiz = quiz_choice?(back) || quiz_fill_blank?(front) unless initialized
|
22
|
-
@quiz
|
23
|
-
end
|
24
|
-
|
25
|
-
def quiz_choice?(back = nil)
|
26
|
-
initialized = [true, false].include? @choice
|
27
|
-
unless initialized
|
28
|
-
@choice = quiz_single_choice?(back) || quiz_multi_choice?(back)
|
29
|
-
end
|
30
|
-
|
31
|
-
@choice
|
32
|
-
end
|
33
|
-
|
34
19
|
def quiz_multi_choice?(back)
|
35
20
|
if back.is_a?(Array) && back.any?
|
36
21
|
back.each { |element| return false unless element[RE_QUIZ_OPTION_M] }
|
@@ -46,33 +31,4 @@ module QuizModule
|
|
46
31
|
end
|
47
32
|
false
|
48
33
|
end
|
49
|
-
|
50
|
-
def quiz_single_choice?(back)
|
51
|
-
if back.is_a?(Array) && back.any?
|
52
|
-
back.each { |element| return false unless element[RE_QUIZ_OPTION_S] }
|
53
|
-
|
54
|
-
# Find at least 1 selected answer
|
55
|
-
selected = back.filter do |element|
|
56
|
-
true if element[RE_SELECTED]
|
57
|
-
end
|
58
|
-
return false unless selected.size == 1
|
59
|
-
|
60
|
-
@quiz = true
|
61
|
-
@front_only = true
|
62
|
-
return true
|
63
|
-
end
|
64
|
-
false
|
65
|
-
end
|
66
|
-
|
67
|
-
def quiz_fill_blank?(front)
|
68
|
-
return true if (front.size == 1) && front.first[/_____/]
|
69
|
-
|
70
|
-
false
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def choices_with_header(back)
|
76
|
-
|
77
|
-
end
|
78
34
|
end
|
@@ -6,8 +6,6 @@ require 'rast/rules/rule'
|
|
6
6
|
require 'rast/rules/rule_evaluator'
|
7
7
|
require 'rast/rules/rule_validator'
|
8
8
|
|
9
|
-
require 'rast/converters/default_converter'
|
10
|
-
|
11
9
|
# Generates the test parameters.
|
12
10
|
class ParameterGenerator
|
13
11
|
# Allow access so yaml-less can build the config via dsl.
|
@@ -24,7 +22,11 @@ class ParameterGenerator
|
|
24
22
|
spec_config = @specs_config[spec_id]
|
25
23
|
|
26
24
|
spec_config[:description] = spec_id
|
25
|
+
|
26
|
+
# Keep, for backwards compatibility
|
27
27
|
spec_config['rules'] ||= spec_config['outcomes']
|
28
|
+
spec_config['default'] ||= spec_config['else']
|
29
|
+
|
28
30
|
spec = instantiate_spec(spec_config)
|
29
31
|
|
30
32
|
list = []
|
@@ -43,13 +45,25 @@ class ParameterGenerator
|
|
43
45
|
private
|
44
46
|
|
45
47
|
def valid_case?(scenario, spec)
|
46
|
-
return true if spec.exclude_clause.nil?
|
48
|
+
return true if spec.exclude_clause.nil? && spec.include_clause.nil?
|
47
49
|
|
48
|
-
exclude_clause = Rule.sanitize(clause: spec.exclude_clause)
|
49
50
|
rule_evaluator = RuleEvaluator.new(converters: spec.converters)
|
50
|
-
rule_evaluator.parse(expression: exclude_clause)
|
51
51
|
|
52
|
-
|
52
|
+
include_result = true
|
53
|
+
unless spec.exclude_clause.nil?
|
54
|
+
exclude_clause = Rule.sanitize(clause: spec.exclude_clause)
|
55
|
+
rule_evaluator.parse(expression: exclude_clause)
|
56
|
+
evaluate_result = rule_evaluator.evaluate(scenario: scenario, rule_token_convert: spec.token_converter)
|
57
|
+
include_result = evaluate_result == 'false'
|
58
|
+
end
|
59
|
+
|
60
|
+
return include_result if spec.include_clause.nil? || !include_result
|
61
|
+
|
62
|
+
include_clause = Rule.sanitize(clause: spec.include_clause)
|
63
|
+
rule_evaluator.parse(expression: include_clause)
|
64
|
+
include_result = rule_evaluator.evaluate(scenario: scenario, rule_token_convert: spec.token_converter) == "true"
|
65
|
+
|
66
|
+
include_result
|
53
67
|
end
|
54
68
|
|
55
69
|
# add all fixtures to the list.
|
@@ -57,7 +71,10 @@ class ParameterGenerator
|
|
57
71
|
validator = RuleValidator.new
|
58
72
|
|
59
73
|
scenarios.each do |scenario|
|
60
|
-
|
74
|
+
good = valid_case?(scenario, spec)
|
75
|
+
# p "#{good} #{scenario}"
|
76
|
+
|
77
|
+
next unless good
|
61
78
|
|
62
79
|
list << build_param(validator, scenario, spec)
|
63
80
|
end
|
@@ -109,7 +126,8 @@ class ParameterGenerator
|
|
109
126
|
spec = RastSpec.new(
|
110
127
|
description: spec_config[:description],
|
111
128
|
variables: spec_config['variables'],
|
112
|
-
rule: Rule.new(rules: spec_config['rules'])
|
129
|
+
rule: Rule.new(rules: spec_config['rules']),
|
130
|
+
default_outcome: spec_config['default'] || spec_config['else']
|
113
131
|
)
|
114
132
|
|
115
133
|
pair_config = calculate_pair(spec_config)
|
@@ -119,6 +137,10 @@ class ParameterGenerator
|
|
119
137
|
spec.init_exclusion(spec_config['exclude'])
|
120
138
|
end
|
121
139
|
|
140
|
+
unless spec_config['include'].nil?
|
141
|
+
spec.init_inclusion(spec_config['include'])
|
142
|
+
end
|
143
|
+
|
122
144
|
converters_config = spec_config['converters']
|
123
145
|
converters = if converters_config.nil?
|
124
146
|
# when no converters defined, we detect if type is consistent, otherwise assume it's string.
|
@@ -130,14 +152,6 @@ class ParameterGenerator
|
|
130
152
|
default_converter
|
131
153
|
end
|
132
154
|
end
|
133
|
-
elsif converters_config.first.class == String
|
134
|
-
# when converters defined, determined by the converter name as String.
|
135
|
-
spec_config['converters'].map do |converter|
|
136
|
-
Object.const_get(converter).new
|
137
|
-
end
|
138
|
-
else
|
139
|
-
# converters defined, probably programmatically when yaml-less, just return it.
|
140
|
-
converters_config
|
141
155
|
end
|
142
156
|
|
143
157
|
spec.init_converters(converters: converters)
|
@@ -157,7 +171,7 @@ class ParameterGenerator
|
|
157
171
|
return { outcomes.first => outcomes.first == 'true' ? 'false' : 'true' }
|
158
172
|
end
|
159
173
|
|
160
|
-
return { outcomes.first => spec_config['
|
174
|
+
return { outcomes.first => spec_config['default'] } if spec_config['default']
|
161
175
|
end
|
162
176
|
|
163
177
|
{}
|
data/lib/rast/rast_spec.rb
CHANGED
@@ -7,17 +7,20 @@ class RastSpec
|
|
7
7
|
# token_converter is the mapping of a variable token to a converter
|
8
8
|
# converters is a list of converters used via positional tokens.
|
9
9
|
attr_reader :variables, :pair, :pair_reversed, :rule, :description,
|
10
|
-
:exclude_clause, :token_converter, :converters
|
10
|
+
:exclude_clause, :include_clause, :token_converter, :converters,
|
11
|
+
:default_outcome
|
11
12
|
|
12
13
|
attr_accessor :exclude
|
13
14
|
|
14
|
-
def initialize(description: '', variables: [][], rule: nil)
|
15
|
+
def initialize(description: '', variables: [][], rule: nil, default_outcome: '')
|
15
16
|
@description = description
|
16
17
|
@variables = variables
|
17
18
|
@pair = {}
|
18
19
|
@pair_reversed = {}
|
19
20
|
@rule = rule
|
20
21
|
@exclude_clause = nil
|
22
|
+
@include_clause = nil
|
23
|
+
@default_outcome = default_outcome
|
21
24
|
end
|
22
25
|
|
23
26
|
def init_pair(pair_config: {})
|
@@ -47,13 +50,8 @@ class RastSpec
|
|
47
50
|
self
|
48
51
|
end
|
49
52
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
Variables: #{@variables}
|
54
|
-
Rules: #{@rules}
|
55
|
-
Pair: #{@pair}
|
56
|
-
Converters: #{@converters}
|
57
|
-
"
|
53
|
+
def init_inclusion(include_clause)
|
54
|
+
@include_clause = include_clause
|
55
|
+
self
|
58
56
|
end
|
59
57
|
end
|
@@ -8,29 +8,29 @@ module LogicHelper
|
|
8
8
|
# * @author Royce
|
9
9
|
# */
|
10
10
|
|
11
|
-
TRUE = '
|
12
|
-
FALSE = '
|
11
|
+
TRUE = '*true'
|
12
|
+
FALSE = '*false'
|
13
13
|
|
14
14
|
# /**
|
15
15
|
# * @param scenario list of scenario tokens.
|
16
16
|
# * @param left_subscript left index.
|
17
17
|
# * @param right_subscript right index.
|
18
|
-
# * @param left left token.
|
19
|
-
# * @param right right token.
|
18
|
+
# * @param left left token, no subscript.
|
19
|
+
# * @param right right token, no subscript.
|
20
20
|
# */
|
21
21
|
def perform_logical_and(scenario: [], left_subscript: -1, right_subscript: -1,
|
22
22
|
left: nil, right: nil)
|
23
|
-
if FALSE == left || FALSE == right
|
23
|
+
if FALSE == left && left_subscript == -1 || FALSE == right && right_subscript == -1
|
24
24
|
FALSE
|
25
|
-
elsif TRUE == left && TRUE == right
|
25
|
+
elsif TRUE == left && left_subscript == -1 && TRUE == right && right_subscript == -1
|
26
26
|
TRUE
|
27
|
-
elsif TRUE == left
|
27
|
+
elsif TRUE == left && left_subscript == -1
|
28
28
|
if right_subscript < 0
|
29
29
|
scenario.include?(right).to_s
|
30
30
|
else
|
31
31
|
(scenario[right_subscript] == right).to_s
|
32
32
|
end
|
33
|
-
elsif TRUE == right
|
33
|
+
elsif TRUE == right && right_subscript == -1
|
34
34
|
if left_subscript < 0
|
35
35
|
scenario.include?(left).to_s
|
36
36
|
else
|
@@ -43,6 +43,8 @@ module LogicHelper
|
|
43
43
|
object: left
|
44
44
|
)
|
45
45
|
|
46
|
+
return 'false' unless left_eval
|
47
|
+
|
46
48
|
right_eval = pevaluate(
|
47
49
|
scenario: scenario,
|
48
50
|
subscript: right_subscript,
|
@@ -62,21 +64,21 @@ module LogicHelper
|
|
62
64
|
# */
|
63
65
|
def perform_logical_or(scenario: [], left_subscript: -1, right_subscript: -1,
|
64
66
|
left: nil, right: nil)
|
65
|
-
if TRUE == left || TRUE == right
|
67
|
+
if TRUE == left && left_subscript == -1 || TRUE == right && right_subscript == -1
|
66
68
|
TRUE
|
67
|
-
elsif FALSE == left && FALSE == right
|
69
|
+
elsif FALSE == left && left_subscript == -1 && FALSE == right && right_subscript == -1
|
68
70
|
FALSE
|
69
|
-
elsif FALSE == left
|
71
|
+
elsif FALSE == left && left_subscript == -1
|
70
72
|
if right_subscript < 0
|
71
73
|
scenario.include?(right).to_s
|
72
74
|
else
|
73
75
|
(scenario[right_subscript] == right).to_s
|
74
76
|
end
|
75
|
-
elsif FALSE == right
|
77
|
+
elsif FALSE == right && right_subscript == -1
|
76
78
|
if left_subscript < 0
|
77
79
|
scenario.include?(left).to_s
|
78
80
|
else
|
79
|
-
(scenario[left_subscript]
|
81
|
+
(scenario[left_subscript] == left).to_s
|
80
82
|
end
|
81
83
|
else
|
82
84
|
left_eval = pevaluate(
|
@@ -85,6 +87,8 @@ module LogicHelper
|
|
85
87
|
object: left
|
86
88
|
)
|
87
89
|
|
90
|
+
return 'true' if left_eval
|
91
|
+
|
88
92
|
right_eval = pevaluate(
|
89
93
|
scenario: scenario,
|
90
94
|
subscript: right_subscript,
|
data/lib/rast/rules/rule.rb
CHANGED
@@ -23,11 +23,11 @@ class Rule
|
|
23
23
|
duplicates = []
|
24
24
|
|
25
25
|
rules.each do |outcome, clause|
|
26
|
-
if duplicates.include?(outcome)
|
27
|
-
|
28
|
-
end
|
26
|
+
# if duplicates.include?(outcome)
|
27
|
+
# raise "#{outcome} matched multiple clauses"
|
28
|
+
# end
|
29
29
|
|
30
|
-
duplicates << outcome
|
30
|
+
# duplicates << outcome
|
31
31
|
|
32
32
|
@outcome_clause_hash[outcome.to_s] = Rule.sanitize(clause: clause)
|
33
33
|
end
|
@@ -35,7 +35,7 @@ class Rule
|
|
35
35
|
|
36
36
|
def self.sanitize(clause: '')
|
37
37
|
return clause if clause.is_a?(Array)
|
38
|
-
|
38
|
+
|
39
39
|
cleaner = Rule.remove_spaces(token: clause, separator: '(')
|
40
40
|
cleaner = Rule.remove_spaces(token: cleaner, separator: ')')
|
41
41
|
cleaner = Rule.remove_spaces(token: cleaner, separator: '&')
|
@@ -72,29 +72,4 @@ class Rule
|
|
72
72
|
def clause(outcome: '')
|
73
73
|
@outcome_clause_hash[outcome]
|
74
74
|
end
|
75
|
-
|
76
|
-
# /**
|
77
|
-
# * Get rule result give a fixed list of scenario tokens. Used for fixed
|
78
|
-
# * list.
|
79
|
-
# *
|
80
|
-
# * @param scenario of interest.
|
81
|
-
# * @return the actionToRuleClauses
|
82
|
-
# */
|
83
|
-
def rule_outcome(scenario: [])
|
84
|
-
scenario_string = scenario.to_s
|
85
|
-
anded_scenario = scenario_string[1..-2].gsub(/,\s/, '&')
|
86
|
-
|
87
|
-
@outcome_clause_hash.each do |key, clause|
|
88
|
-
or_list_clause = clause.split('\|').map(&:strip)
|
89
|
-
return key if or_list_clause.include?(anded_scenario)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# /**
|
94
|
-
# * @see {@link Object#toString()}
|
95
|
-
# * @return String representation of this instance.
|
96
|
-
# */
|
97
|
-
def to_s
|
98
|
-
@outcome_clause_hash.to_s
|
99
|
-
end
|
100
75
|
end
|
@@ -21,7 +21,7 @@ class RuleEvaluator
|
|
21
21
|
|
22
22
|
# the "false" part of the "false[1]"
|
23
23
|
RE_TOKEN_BODY = /^.+(?=\[)/.freeze
|
24
|
-
RE_TOKENS = /([!|)(&])|([a-zA-Z\s0-9-]+\[\d\])/.freeze
|
24
|
+
RE_TOKENS = /([!|)(&])|([*a-zA-Z\s0-9-]+\[\d\])/.freeze
|
25
25
|
|
26
26
|
def self.operator_from_symbol(symbol: nil)
|
27
27
|
OPERATORS.find { |operator| operator.symbol == symbol }
|
@@ -30,7 +30,7 @@ class RuleEvaluator
|
|
30
30
|
DEFAULT_CONVERT_HASH = {
|
31
31
|
Integer => IntConverter.new,
|
32
32
|
Float => FloatConverter.new,
|
33
|
-
Fixnum =>
|
33
|
+
Fixnum => IntConverter.new,
|
34
34
|
Array => DefaultConverter.new,
|
35
35
|
TrueClass => BoolConverter.new,
|
36
36
|
FalseClass => BoolConverter.new,
|
@@ -85,10 +85,7 @@ class RuleEvaluator
|
|
85
85
|
# * @return <code>String</code> representation of the result
|
86
86
|
# */
|
87
87
|
def evaluate(scenario: [], rule_token_convert: {})
|
88
|
-
|
89
|
-
if @stack_rpn.empty?
|
90
|
-
true
|
91
|
-
elsif @stack_rpn.size == 1
|
88
|
+
if @stack_rpn.size == 1
|
92
89
|
evaluate_one_rpn(scenario: scenario).to_s
|
93
90
|
else
|
94
91
|
evaluate_multi_rpn(
|
@@ -112,18 +109,14 @@ class RuleEvaluator
|
|
112
109
|
if TRUE != value && FALSE != value
|
113
110
|
subscript = extract_subscript(token: value.to_s)
|
114
111
|
value_str = value.to_s.strip
|
115
|
-
if subscript > -1
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
default_converter.convert(value_str)
|
122
|
-
else
|
123
|
-
rule_token_convert[value_str].convert(value_str)
|
124
|
-
end
|
125
|
-
end
|
112
|
+
value = if subscript > -1
|
113
|
+
value_token = value_str[/^.+(?=\[)/]
|
114
|
+
rule_token_convert[value_token].convert(value_token)
|
115
|
+
else
|
116
|
+
rule_token_convert[value_str].convert(value_str)
|
117
|
+
end
|
126
118
|
end
|
119
|
+
|
127
120
|
retval << subscript
|
128
121
|
retval << value
|
129
122
|
retval
|
@@ -161,6 +154,8 @@ class RuleEvaluator
|
|
161
154
|
# * @param string token to check for subscript.
|
162
155
|
# */
|
163
156
|
def extract_subscript(token: '')
|
157
|
+
return -1 if token.is_a? Array
|
158
|
+
|
164
159
|
subscript = token[/\[(\d+)\]$/, 1]
|
165
160
|
subscript.nil? ? -1 : subscript.to_i
|
166
161
|
end
|
@@ -177,9 +172,6 @@ class RuleEvaluator
|
|
177
172
|
stack_rpn_clone = Marshal.load(Marshal.dump(@stack_rpn))
|
178
173
|
|
179
174
|
# /* evaluating the RPN expression */
|
180
|
-
|
181
|
-
# binding.pry
|
182
|
-
|
183
175
|
while stack_rpn_clone.any?
|
184
176
|
token = stack_rpn_clone.pop
|
185
177
|
if operator?(token: token)
|
@@ -211,7 +203,8 @@ class RuleEvaluator
|
|
211
203
|
def evaluate_multi(scenario: [], rule_token_convert: {}, operator: nil)
|
212
204
|
default_converter = DEFAULT_CONVERT_HASH[scenario.first.class]
|
213
205
|
|
214
|
-
#
|
206
|
+
# Convert 'nil' to nil.
|
207
|
+
formatted_scenario = scenario.map { |token| token == 'nil' ? nil: token }
|
215
208
|
|
216
209
|
left_arr = next_value(
|
217
210
|
rule_token_convert: rule_token_convert,
|
@@ -225,17 +218,17 @@ class RuleEvaluator
|
|
225
218
|
|
226
219
|
answer = send(
|
227
220
|
"perform_logical_#{operator.name}",
|
228
|
-
scenario:
|
221
|
+
scenario: formatted_scenario,
|
229
222
|
left_subscript: left_arr[0],
|
230
223
|
right_subscript: right_arr[0],
|
231
224
|
left: left_arr[1],
|
232
225
|
right: right_arr[1]
|
233
226
|
)
|
234
227
|
|
235
|
-
@stack_answer << if answer[0] == '
|
228
|
+
@stack_answer << if answer[0] == '*'
|
236
229
|
answer
|
237
230
|
else
|
238
|
-
"
|
231
|
+
"*#{answer}"
|
239
232
|
end
|
240
233
|
end
|
241
234
|
|
@@ -243,29 +236,28 @@ class RuleEvaluator
|
|
243
236
|
# * @param scenario List of values to evaluate against the rule expression.
|
244
237
|
# */
|
245
238
|
def evaluate_multi_not(scenario: [])
|
246
|
-
|
247
|
-
|
248
|
-
# binding.pry
|
239
|
+
latest = @stack_answer.pop.strip
|
249
240
|
|
250
|
-
answer = if LogicHelper::TRUE ==
|
241
|
+
answer = if LogicHelper::TRUE == latest
|
251
242
|
LogicHelper::FALSE
|
252
|
-
elsif LogicHelper::FALSE ==
|
243
|
+
elsif LogicHelper::FALSE == latest
|
253
244
|
LogicHelper::TRUE
|
254
245
|
else
|
255
|
-
subscript = extract_subscript(token:
|
246
|
+
subscript = extract_subscript(token: latest)
|
247
|
+
converter = DEFAULT_CONVERT_HASH[scenario.first.class]
|
256
248
|
if subscript < 0
|
257
|
-
|
249
|
+
converted = converter.convert(latest)
|
250
|
+
(!scenario.include?(converted)).to_s
|
258
251
|
else
|
259
|
-
|
260
|
-
|
261
|
-
(scenario[subscript] == converted).to_s
|
252
|
+
converted = converter.convert(latest[RE_TOKEN_BODY])
|
253
|
+
(scenario[subscript] != converted).to_s
|
262
254
|
end
|
263
255
|
end
|
264
256
|
|
265
|
-
@stack_answer << if answer[0] == '
|
257
|
+
@stack_answer << if answer[0] == '*'
|
266
258
|
answer
|
267
259
|
else
|
268
|
-
"
|
260
|
+
"*#{answer}"
|
269
261
|
end
|
270
262
|
end
|
271
263
|
|
@@ -26,8 +26,6 @@ class RuleValidator
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def validate_multi(scenario: [], spec: nil, rule_result: [])
|
29
|
-
# binding.pry
|
30
|
-
|
31
29
|
matched_outputs = []
|
32
30
|
match_count = 0
|
33
31
|
|
@@ -37,26 +35,20 @@ class RuleValidator
|
|
37
35
|
match_count += 1
|
38
36
|
matched_outputs << spec.rule.outcomes[i]
|
39
37
|
end
|
40
|
-
Rast.assert("Scenario must fall into a unique rule output/clause:
|
41
|
-
#{scenario} , matched: #{matched_outputs}") { match_count == 1 }
|
42
38
|
|
43
|
-
matched_outputs
|
39
|
+
Rast.assert("#{spec.description} #{scenario} must fall into a unique rule outcome/clause, matched: #{matched_outputs}") do
|
40
|
+
match_count == 1 || match_count == 0 && !spec.default_outcome.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
matched_outputs.first || spec.default_outcome
|
44
44
|
end
|
45
45
|
|
46
|
+
#
|
46
47
|
def binary_outcome(outcome: '', spec: nil, expected: false)
|
47
|
-
|
48
|
-
|
49
|
-
expected == 'true' ? outcome : opposite(outcome: outcome, spec: spec)
|
48
|
+
if expected == 'true'
|
49
|
+
outcome
|
50
50
|
else
|
51
|
-
expected == 'true' ? opposite(outcome: outcome, spec: spec) : outcome
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def opposite(outcome: '', spec: nil)
|
56
|
-
if spec.pair.keys.include? outcome
|
57
51
|
spec.pair[outcome]
|
58
|
-
else
|
59
|
-
spec.pair_reversed[outcome]
|
60
52
|
end
|
61
53
|
end
|
62
54
|
end
|
data/lib/rast/spec_dsl.rb
CHANGED
@@ -8,10 +8,10 @@ class SpecDSL
|
|
8
8
|
include FactoryGirl::Syntax::Methods
|
9
9
|
|
10
10
|
attr_accessor :subject, :rspec_methods, :execute_block,
|
11
|
-
:prepare_block, :transients, :outcomes, :fixtures
|
11
|
+
:prepare_block, :transients, :outcomes, :fixtures, :spec_id
|
12
12
|
|
13
13
|
# # yaml-less
|
14
|
-
attr_writer :variables, :exclude, :converters, :rules, :pair
|
14
|
+
attr_writer :variables, :exclude, :include, :converters, :rules, :pair, :default_outcome
|
15
15
|
|
16
16
|
# @subject the sut instance
|
17
17
|
# @name the sut name to be displayed with -fd
|
@@ -29,23 +29,6 @@ class SpecDSL
|
|
29
29
|
instance_eval(&block)
|
30
30
|
end
|
31
31
|
|
32
|
-
def respond_to_missing?(*several_variants)
|
33
|
-
super(several_variants)
|
34
|
-
end
|
35
|
-
|
36
|
-
def method_missing(method_name_symbol, *args, &block)
|
37
|
-
# p "method_missing: #{method_name_symbol}"
|
38
|
-
return super if method_name_symbol == :to_ary
|
39
|
-
|
40
|
-
@rspec_methods << {
|
41
|
-
name: method_name_symbol,
|
42
|
-
args: args.first,
|
43
|
-
block: block
|
44
|
-
}
|
45
|
-
|
46
|
-
self
|
47
|
-
end
|
48
|
-
|
49
32
|
# yaml-less start
|
50
33
|
def variables(vars)
|
51
34
|
@variables = vars
|
@@ -55,16 +38,18 @@ class SpecDSL
|
|
55
38
|
@exclude = clause
|
56
39
|
end
|
57
40
|
|
58
|
-
def converters(&block)
|
59
|
-
@converters = instance_eval(&block)
|
60
|
-
end
|
61
|
-
|
62
41
|
def rules(rules)
|
63
|
-
@rules =
|
42
|
+
@rules = {}
|
43
|
+
|
44
|
+
rules.each do |key, value|
|
45
|
+
calc_key = key
|
46
|
+
calc_key = key == :true if key == :true || key == :false
|
47
|
+
@rules[calc_key] = value
|
48
|
+
end
|
64
49
|
end
|
65
50
|
|
66
|
-
def
|
67
|
-
|
51
|
+
def outcomes(outcomes)
|
52
|
+
rules(outcomes)
|
68
53
|
end
|
69
54
|
|
70
55
|
# yaml-less end
|
@@ -91,9 +76,17 @@ class SpecDSL
|
|
91
76
|
end
|
92
77
|
|
93
78
|
@fixtures.sort_by! do |fixture|
|
79
|
+
|
80
|
+
if fixture[:expected_outcome].nil?
|
81
|
+
raise 'Broken initialization, check your single rule/else/default configuration'
|
82
|
+
end
|
83
|
+
|
94
84
|
fixture[:expected_outcome] + fixture[:scenario].to_s
|
85
|
+
# fixture[:scenario].to_s + fixture[:expected_outcome]
|
95
86
|
end
|
96
87
|
|
88
|
+
# @fixtures.reverse!
|
89
|
+
|
97
90
|
generate_rspecs
|
98
91
|
end
|
99
92
|
|
@@ -103,8 +96,13 @@ class SpecDSL
|
|
103
96
|
main_scope = self
|
104
97
|
|
105
98
|
title = "#{@subject_name}: #{@fixtures.first[:spec].description}"
|
99
|
+
|
106
100
|
exclusion = fixtures.first[:spec].exclude_clause
|
107
|
-
|
101
|
+
exclusion = exclusion.join if exclusion.is_a? Array
|
102
|
+
title += ", EXCLUDE: '#{exclusion}'" if exclusion
|
103
|
+
inclusion = fixtures.first[:spec].include_clause
|
104
|
+
inclusion = inclusion.join if inclusion.is_a? Array
|
105
|
+
title += ", ONLY: '#{inclusion}'" if inclusion
|
108
106
|
|
109
107
|
RSpec.describe title do
|
110
108
|
main_scope.fixtures.each do |fixture|
|
@@ -129,6 +127,7 @@ def generate_rspec(scope: nil, scenario: {}, expected: '')
|
|
129
127
|
block_params = scenario.values
|
130
128
|
|
131
129
|
@mysubject = scope.subject
|
130
|
+
|
132
131
|
class << self
|
133
132
|
define_method(:subject) { @mysubject }
|
134
133
|
end
|
data/lib/rast.rb
CHANGED
@@ -8,10 +8,6 @@ require 'rast/spec_dsl'
|
|
8
8
|
class Rast
|
9
9
|
# RSpec All scenario test
|
10
10
|
#
|
11
|
-
# Example:
|
12
|
-
# >> Hola.hi("spanish")
|
13
|
-
# => hola mundo
|
14
|
-
#
|
15
11
|
# Arguments:
|
16
12
|
# language: (String)
|
17
13
|
|
@@ -38,6 +34,10 @@ class Rast
|
|
38
34
|
raise message unless yield
|
39
35
|
end
|
40
36
|
|
37
|
+
def xspec(id, &block)
|
38
|
+
p "xspec skipped #{id}"
|
39
|
+
end
|
40
|
+
|
41
41
|
def spec(id, &block)
|
42
42
|
global_spec(
|
43
43
|
subject: @subject,
|
data/lib/template_spec.yml
CHANGED
@@ -5,14 +5,12 @@ specs:
|
|
5
5
|
|
6
6
|
variables:
|
7
7
|
param1:
|
8
|
-
-
|
9
|
-
-
|
8
|
+
- one
|
9
|
+
- two
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
outcomes: # required (dictionary)
|
12
|
+
true: true[0]
|
13
13
|
|
14
|
-
|
15
|
-
'true': 'true[0]'
|
14
|
+
pair: {true: one} # optional. (dictionary with 1 mapping)
|
16
15
|
|
17
|
-
pair
|
18
|
-
'true': 'false'
|
16
|
+
default: DEFAULT # optional (scalar) fall off value, or the else result of the `pair` config.
|
data/rast.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Royce Remulla
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: factory_girl
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- CHANGELOG.md
|
39
39
|
- Gemfile
|
40
40
|
- Gemfile.lock
|
41
|
+
- Getting-Started.md
|
41
42
|
- README.md
|
42
43
|
- examples/arithmetic_module.rb
|
43
44
|
- examples/double_example.rb
|
@@ -45,8 +46,10 @@ files:
|
|
45
46
|
- examples/factory_example.rb
|
46
47
|
- examples/logic_checker.rb
|
47
48
|
- examples/logic_four.rb
|
49
|
+
- examples/lohika.rb
|
48
50
|
- examples/phone.rb
|
49
51
|
- examples/positive.rb
|
52
|
+
- examples/positive2.rb
|
50
53
|
- examples/prime_number.rb
|
51
54
|
- examples/quiz_module.rb
|
52
55
|
- examples/quoted.rb
|