rast 0.15.1 → 0.18.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: c182ee63c3f1cb18ff294cd27be2dca33553cc9f15295ee50848274fdf0a6b90
4
- data.tar.gz: 69de9f82cbc4dd45c5c8ac8a275a2eb586fe5cd531ec3ef52638b70c035e7ebd
3
+ metadata.gz: 77e02b108231f3d36dd5e490e9a0ab9cf1334ebb53ae6ffce93cbec2c124423b
4
+ data.tar.gz: a738c7603f64ec75eff5a13c87fa472d147c1262efb0340d2b500d76fa590826
5
5
  SHA512:
6
- metadata.gz: ee7e4c7fd412ef942a78be18fddaac3b58718724f2ad89f83f728781a347913202d80abe81c99d1b43bbe06030d7de8cfbd735a3a87f9f3ebe0b39b19bd5bf1b
7
- data.tar.gz: 4cd8885dab731bf4a4c3bed1d2402099545a0bc53b97a26207f99e4190f8508bdc1c2ae2b687bd51f441d29438d313335aa384e470aee298a4a433b411bfa7d3
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.
@@ -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
+
@@ -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
@@ -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
@@ -3,8 +3,6 @@
3
3
  # Example single number answer
4
4
  class Positive
5
5
  def positive?(number)
6
- raise "Invalid parameter: #{number}" unless number.is_a?(Integer) || number.is_a?(Float)
7
-
8
6
  number > 0
9
7
  end
10
8
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example single number answer
4
+ class Positive2
5
+ def positive?(number)
6
+ raise 'Invalid' unless number.is_a? Numeric
7
+
8
+ number > 0
9
+ end
10
+ end
@@ -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
@@ -3,6 +3,6 @@
3
3
  # Converts string into number
4
4
  class StrConverter
5
5
  def convert(string)
6
- string
6
+ string unless string == 'nil'
7
7
  end
8
8
  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
- rule_evaluator.evaluate(scenario: scenario, rule_token_convert: spec.token_converter) == "false"
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
- next unless valid_case?(scenario, spec)
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['else'] } if spec_config['else']
174
+ return { outcomes.first => spec_config['default'] } if spec_config['default']
161
175
  end
162
176
 
163
177
  {}
@@ -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 to_s
51
- "Class: #{self.class}
52
- Description: #{@description}
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 = '|true'
12
- FALSE = '|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]).to_s == left
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,
@@ -23,11 +23,11 @@ class Rule
23
23
  duplicates = []
24
24
 
25
25
  rules.each do |outcome, clause|
26
- if duplicates.include?(outcome)
27
- raise "#{outcome} matched multiple clauses"
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 => FloatConverter.new,
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
- # /* check if is there something to evaluate */
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
- converter = @converters[subscript]
117
- value = converter.convert(value_str[/^.+(?=\[)/])
118
- else
119
- value = if rule_token_convert.nil? ||
120
- rule_token_convert[value_str].nil?
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
- # binding.pry
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: 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
- "|#{answer}"
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
- left = @stack_answer.pop.strip
247
-
248
- # binding.pry
239
+ latest = @stack_answer.pop.strip
249
240
 
250
- answer = if LogicHelper::TRUE == left
241
+ answer = if LogicHelper::TRUE == latest
251
242
  LogicHelper::FALSE
252
- elsif LogicHelper::FALSE == left
243
+ elsif LogicHelper::FALSE == latest
253
244
  LogicHelper::TRUE
254
245
  else
255
- subscript = extract_subscript(token: left)
246
+ subscript = extract_subscript(token: latest)
247
+ converter = DEFAULT_CONVERT_HASH[scenario.first.class]
256
248
  if subscript < 0
257
- (!scenario.include?(left)).to_s
249
+ converted = converter.convert(latest)
250
+ (!scenario.include?(converted)).to_s
258
251
  else
259
- default_converter = DEFAULT_CONVERT_HASH[scenario.first.class]
260
- converted = default_converter.convert(left[RE_TOKEN_BODY])
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
- "|#{answer}"
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.first
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
- is_positive = spec.pair.keys.include?(outcome)
48
- if is_positive
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 = 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 pair(pair)
67
- @pair = pair
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
- title += ", Excluded: '#{exclusion}'" if exclusion
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,
@@ -5,14 +5,12 @@ specs:
5
5
 
6
6
  variables:
7
7
  param1:
8
- - false
9
- - true
8
+ - one
9
+ - two
10
10
 
11
- converters:
12
- - BoolConverter
11
+ outcomes: # required (dictionary)
12
+ true: true[0]
13
13
 
14
- rules:
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'rast'
5
- spec.version = '0.15.1'
5
+ spec.version = '0.18.0'
6
6
  spec.authors = ['Royce Remulla']
7
7
  spec.email = ['royce.com@gmail.com']
8
8
 
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.15.1
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-09 00:00:00.000000000 Z
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