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 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