Clean unit tests

This commit is contained in:
Glenn Y. Rolland 2019-12-21 16:55:15 +01:00
parent a7a32c5541
commit e0498abb05
40 changed files with 246 additions and 182 deletions

View File

@ -1 +1 @@
ruby 2.4.2
ruby 2.6.1

View File

@ -1,11 +1,6 @@
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem 'thor'
gem 'rly'
gem 'pry'
gem 'opal'
gem 'rubocop'
gem 'rubocop-rspec'
# Specify your gem's dependencies in mm2ep_depend.gemspec
gemspec

View File

@ -1,8 +1,15 @@
# Mm2epDepend
# Namarara
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/mm2ep_depend`. To experiment with that code, run `bin/console` for an interactive prompt.
Namarara is a library that parses boolean expressions, builds an [binary
expression tree](https://en.wikipedia.org/wiki/Binary_expression_tree).
Namare can also evalutes a result from a set of values corresponding the
variables used within the boolean expression.
2. binary expression
and computes a boolean result from this AST.
TODO: Delete this and the text above, and describe your gem
## Installation
@ -22,6 +29,64 @@ Or install it yourself as:
## Usage
### Evaluate a single expression
```
# Initialize Namarara
namarara = Namarara.new
# Build the binary expression tree (BET)
namarara_bet = namarara.parse('this AND (that OR other) AND something_else')
# Prepare variables
variables = {
this: true,
that: false,
other: false,
something_else: true
}
# Compute tree with variables
result = namarara_bet.compute(variables)
```
### Evaluating a set of rules
```ruby
# Initialize Namarara
namarara = Namarara::Parser.new(Namarara::Lexer.new)
# A set of rules i want to check
rules = [
{name: 'vulnetable_person', expr: 'is_adult AND is_subordinate'},
{name: 'has_constraints', expr: 'is_adult AND has_children' },
{name: 'is_child', expr: 'NOT is_adult'}
# ...
]
# A set of values i want to inject (values can come from HTTP or from database
# as long as they are expressed as strings)
namarara.names = {
"is_adult" => 'false',
"is_subordinate" => 'true',
"has_children" => 'true'
}
rules.map do |rule|
namarara_bet = namarara.parse(rule)
result = namarara_bet.compute
if result then
warnings << "Rule #{rule} is true"
end
end
if not warnings.empty?
puts "Attention: vous collectez des DCP de personnes vulnerables"
puts warnings.join("\n")
else
puts "Rien à dire :-)"
end
TODO: Write usage instructions here
## Development

46
demo.rb Normal file
View File

@ -0,0 +1,46 @@
$:.insert(0, 'lib')
require 'mm2ep_depend'
def verify_input
parser = Mm2ep::Depend::Parser.new(Mm2ep::Depend::Lexer.new)
# on démarre avec zéro alertes
warnings = []
# ma liste de regles pour lesquelles je veux des alertes
rules = [
'est_adulte AND est_subordone',
'est_adulte AND a_des_enfants',
'NOT est_adulte'
# ...
]
# contexte récupéré en HTTP ou en base de données
context = {
"est_adulte" => 'false',
"est_subordone" => 'true',
"a_des_enfants" => 'true'
# 80 valeurs de plus si on veut
}
rules.each do |rule|
parser.names = context
token = parser.parse(rule)
res = token.compute
if res then
warnings << "La règle #{rule} n'est pas respectée"
end
end
if not warnings.empty?
puts "Attention: vous collectez des DCP de personnes vulnerables"
puts warnings.join("\n")
else
puts "Rien à dire :-)"
end
end
verify_input()

View File

@ -171,14 +171,9 @@ module Mm2ep
end
end
# Cut HERE
# 8< ---- 8< ---- ...
class Parser < Rly::Yacc
attr_writer :names

View File

@ -1,18 +1,18 @@
# coding: utf-8
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "mm2ep_depend/version"
require 'mm2ep_depend/version'
Gem::Specification.new do |spec|
spec.name = "mm2ep_depend"
spec.name = 'mm2ep_depend'
spec.version = Mm2epDepend::VERSION
spec.authors = ["Roguelearg"]
spec.authors = ['Roguelearg']
spec.email = ["torre.brendon@gmail.com"]
spec.summary = %q{A library and tools for expressions}
spec.description = %q{A library and tools for expressions}
spec.homepage = "https://datatransition.net"
spec.license = "MIT"
spec.homepage = 'https://datatransition.net'
spec.license = 'MIT'
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
@ -26,11 +26,18 @@ Gem::Specification.new do |spec|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.bindir = "exe"
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.require_paths = ['lib']
spec.add_development_dependency 'thor'
spec.add_development_dependency 'rly'
spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "minitest", "~> 5.0"
spec.add_development_dependency "opal"
spec.add_development_dependency "pry"
spec.add_development_dependency "rubocop"
spec.add_development_dependency "rubocop-rspec"
end

View File

@ -0,0 +1,8 @@
---
title: "Nom de la règle"
expr: "est_adulte AND a_des_enfants"
alert:
text: "Attention, votre adulte a des enfants"
explanation: |
Blablabla sur
plsuieurs lignes

View File

@ -1 +0,0 @@
false = "Arya"

View File

@ -1 +0,0 @@
( a_girl_has_no_name = true ) ANDAND ( character = "Arya Stark" ) OR false AND true

View File

@ -1 +0,0 @@
?

View File

@ -1 +0,0 @@
AND

View File

@ -1 +0,0 @@
=

View File

@ -1 +0,0 @@
false False

View File

@ -1 +0,0 @@
(

View File

@ -1 +0,0 @@
NOT

View File

@ -1 +0,0 @@
7

View File

@ -1 +0,0 @@
OR

View File

@ -1 +0,0 @@
)

View File

@ -1 +0,0 @@
"Arya Stark"

View File

@ -1 +0,0 @@
true True

View File

@ -1 +0,0 @@
character

View File

@ -1 +0,0 @@
NOT false AND NOT false

View File

@ -1 +0,0 @@
NOT true OR NOT true

View File

@ -1 +0,0 @@
false OR true AND false

View File

@ -1 +0,0 @@
false OR false AND true OR true

View File

@ -1 +0,0 @@
character = true

View File

@ -1 +0,0 @@
nombre = 10

View File

@ -1 +0,0 @@
a_girl_has_no_name = "Arya Stark"

View File

@ -1 +0,0 @@
a_girl_has_no_name AND character

View File

@ -1 +0,0 @@
a_girl_has_no_name OR character

View File

@ -1 +0,0 @@
false

View File

@ -1 +0,0 @@
NOT true

View File

@ -1 +0,0 @@
( true )

View File

@ -1 +0,0 @@
true

View File

@ -1 +0,0 @@
a_girl_has_no_name

View File

@ -1,2 +1 @@
( a_girl_has_no_name = true ) AND ( character = "Arya Stark" )

View File

@ -7,88 +7,76 @@ describe Mm2ep::Depend::Lexer do
end
it 'has to recognize AND operator' do
line = File
.read(testfile('success_lexer_and_op.txt')).delete("\n")
lexer.input(line.chomp)
line = 'AND'
lexer.input(line)
assert_equal('AND_OP', lexer.next.type.to_s)
end
it 'has to recognize EQ operator' do
line = File
.read(testfile('success_lexer_eq_op.txt')).delete("\n")
lexer.input(line.chomp)
line = '='
lexer.input(line)
assert_equal('EQ_OP', lexer.next.type.to_s)
end
it 'has to recognize OR operator' do
line = File
.read(testfile('success_lexer_or_op.txt')).delete("\n")
lexer.input(line.chomp)
line = 'OR'
lexer.input(line)
assert_equal('OR_OP', lexer.next.type.to_s)
end
it 'has to recognize NOT operator' do
line = File
.read(testfile('success_lexer_not_op.txt')).delete("\n")
lexer.input(line.chomp)
line = 'NOT'
lexer.input(line)
assert_equal('NOT_OP', lexer.next.type.to_s)
end
it 'has to recognize false boolean' do
line = File
.read(testfile('success_lexer_f_bool.txt')).delete("\n")
lexer.input(line.chomp)
line = 'false False'
lexer.input(line)
assert_equal('F_BOOL', lexer.next.type.to_s)
assert_equal('F_BOOL', lexer.next.type.to_s)
end
it 'has to recognize true boolean' do
line = File
.read(testfile('success_lexer_t_bool.txt')).delete("\n")
lexer.input(line.chomp)
line = 'true True'
lexer.input(line)
assert_equal('T_BOOL', lexer.next.type.to_s)
assert_equal('T_BOOL', lexer.next.type.to_s)
end
it 'has to recognize left parenthesis' do
line = File
.read(testfile('success_lexer_l_par.txt')).delete("\n")
lexer.input(line.chomp)
line = '('
lexer.input(line)
assert_equal('L_PAR', lexer.next.type.to_s)
end
it 'has to recognize right parenthesis' do
line = File
.read(testfile('success_lexer_r_par.txt')).delete("\n")
lexer.input(line.chomp)
line = ')'
lexer.input(line)
assert_equal('R_PAR', lexer.next.type.to_s)
end
it 'has to recognize number' do
line = File
.read(testfile('success_lexer_number.txt')).delete("\n")
lexer.input(line.chomp)
line = '7'
lexer.input(line)
assert_equal('NUMBER', lexer.next.type.to_s)
end
it 'has to recognize string' do
line = File
.read(testfile('success_lexer_string.txt')).delete("\n")
line = %("Arya Stark")
lexer.input(line.chomp)
assert_equal('STRING', lexer.next.type.to_s)
end
it 'has to recognize var' do
line = File
.read(testfile('success_lexer_var.txt')).delete("\n")
lexer.input(line.chomp)
line = 'character'
lexer.input(line)
assert_equal('VAR', lexer.next.type.to_s)
end
it 'has to recognize illegal character and replace them with erase them' do
line = File
.read(testfile('error_lexer_illegal_character.txt')).delete("\n")
lexer.input(line.chomp)
line = '?'
lexer.input(line)
assert_equal('', lexer.next.to_s)
end
end

View File

@ -9,19 +9,18 @@ describe Mm2ep::Depend::Parser do
end
it 'has to report var which is not defined' do
line = File
.read(testfile('success_simple_eq_expr_boolexpr.txt')).delete("\n")
line = 'character = true'
parser.names = {}
token = parser.parse(line.chomp)
token = parser.parse(line)
errors = token.errors.select { |el| el.is_a? Mm2ep::Depend::VarNotDefined }
errors.size.must_equal 1
errors[0].var.must_equal 'character'
end
it 'has to report vars which are not defined' do
line = File.read(testfile('success_simple_expr_or_expr.txt')).delete("\n")
line = 'a_girl_has_no_name AND character'
parser.names = {}
token = parser.parse(line.chomp)
token = parser.parse(line)
errors = token.errors.select { |el| el.is_a? Mm2ep::Depend::VarNotDefined }
errors.size.must_equal 2
errors[0].var.must_equal 'a_girl_has_no_name'
@ -29,10 +28,10 @@ describe Mm2ep::Depend::Parser do
end
it 'has to report invalid_grammar' do
line = File
.read(testfile('error_grammar_partially_invalid.txt')).delete("\n")
line = '( a_girl_has_no_name = true ) ' \
'ANDAND ( character = "Arya Stark" ) OR false AND true'
parser.names = { 'a_girl_has_no_name' => true, 'character' => 'Arya Stark' }
token = parser.parse(line.chomp)
token = parser.parse(line)
parser.check_grammar line, token
token.errors.select do |elem|
elem.is_a? Mm2ep::Depend::InvalidGrammar
@ -40,10 +39,9 @@ describe Mm2ep::Depend::Parser do
end
it 'has to be nil when grammar is completely invalid' do
line = File
.read(testfile('error_grammar_completely_invalid.txt')).delete("\n")
line = 'false = "Arya"'
parser.names = {}
token = parser.parse(line.chomp)
token = parser.parse(line)
parser.check_grammar line, token
assert_nil token
end

View File

@ -3,22 +3,18 @@ require 'mm2ep_depend'
describe Mm2ep::Depend::Parser do
let(:parser) do
Mm2ep::Depend::Parser.new(
Mm2ep::Depend::Lexer.new
)
Mm2ep::Depend::Parser.new(Mm2ep::Depend::Lexer.new)
end
it 'has to do not before or' do
line = File
.read(testfile('success_priority_not_or.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'NOT true OR NOT true'
token = parser.parse(line)
assert_equal('( NOT ( bool:true ) ) OR ( NOT ( bool:true ) )', token.to_s)
end
it 'has to do not before and' do
line = File
.read(testfile('success_priority_not_and.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'NOT false AND NOT false'
token = parser.parse(line)
assert_equal(
'( NOT ( bool:false ) ) AND ( NOT ( bool:false ) )',
token.to_s
@ -26,9 +22,8 @@ describe Mm2ep::Depend::Parser do
end
it 'has to do and before or' do
line = File
.read(testfile('success_priority_or_and.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'false OR true AND false'
token = parser.parse(line)
assert_equal(
'( bool:false ) OR ( ( bool:true ) AND ( bool:false ) )',
token.to_s
@ -36,9 +31,8 @@ describe Mm2ep::Depend::Parser do
end
it 'has to do and before or operators' do
line = File
.read(testfile('success_priority_or_and_or.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'false OR false AND true OR true'
token = parser.parse(line)
assert_equal(
'( ( bool:false ) OR ( ( bool:false ) '\
'AND ( bool:true ) ) ) OR ( bool:true )',

View File

@ -9,158 +9,155 @@ describe Mm2ep::Depend::Parser do
end
it 'has to find var and compute it to expr' do
line = File
.read(testfile('success_simple_var_expr.txt')).delete("\n")
line = 'a_girl_has_no_name'
parser.names = { 'a_girl_has_no_name' => 'true' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to find true boolean and compute it to expr' do
line = File
.read(testfile('success_simple_t_bool_expr.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'true'
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to find false boolean and compute it to expr' do
line = File
.read(testfile('success_simple_f_bool_expr.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'false'
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to find parenthesis expr and compute it to expr' do
line = File
.read(testfile('success_simple_parenthesis_expr.txt')).delete("\n")
token = parser.parse(line.chomp)
line = '( true )'
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to apply not on expr' do
line = File
.read(testfile('success_simple_not_expr.txt')).delete("\n")
token = parser.parse(line.chomp)
line = 'NOT true'
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate eq with bool expr and return true' do
line = File
.read(testfile('success_simple_eq_expr_boolexpr.txt')).delete("\n")
line = 'character = true'
parser.names = { 'character' => 'true' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to evaluate eq with bool expr and return false' do
line = File
.read(testfile('success_simple_eq_expr_boolexpr.txt')).delete("\n")
line = 'character = true'
parser.names = { 'character' => 'false' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate eq with number and return true' do
line = File
.read(testfile('success_simple_eq_expr_number.txt')).delete("\n")
line = 'nombre = 10'
parser.names = { 'nombre' => '10' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to evaluate eq with number and return false' do
line = File
.read(testfile('success_simple_eq_expr_number.txt')).delete("\n")
line = 'nombre = 10'
parser.names = { 'nombre' => '11' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate eq with string and return true' do
line = File
.read(testfile('success_simple_eq_expr_string.txt')).delete("\n")
line = 'a_girl_has_no_name = "Arya Stark"'
parser.names = { 'a_girl_has_no_name' => 'Arya Stark' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to evaluate eq with string and return false' do
line = File
.read(testfile('success_simple_eq_expr_string.txt')).delete("\n")
line = 'a_girl_has_no_name = "Arya Stark"'
parser.names = { 'a_girl_has_no_name' => 'Sansa Stark' }
token = parser.parse(line.chomp)
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate true OR true and return true' do
line = File
.read(testfile('success_simple_expr_or_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'true',
'character' => 'true' }
line = 'a_girl_has_no_name OR character'
parser.names = {
'a_girl_has_no_name' => 'true',
'character' => 'true'
}
token = parser.parse(line.chomp)
assert_equal(true, token.compute)
end
it 'has to evaluate true OR false and return true' do
line = File
.read(testfile('success_simple_expr_or_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'true',
'character' => 'false' }
line = 'a_girl_has_no_name OR character'
parser.names = {
'a_girl_has_no_name' => 'true',
'character' => 'false'
}
token = parser.parse(line.chomp)
assert_equal(true, token.compute)
end
it 'has to evaluate false OR true and return true' do
line = File
.read(testfile('success_simple_expr_or_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'false',
'character' => 'true' }
line = 'a_girl_has_no_name OR character'
parser.names = {
'a_girl_has_no_name' => 'false',
'character' => 'true'
}
token = parser.parse(line.chomp)
assert_equal(true, token.compute)
end
it 'has to evaluate false OR false and return false' do
line = File
.read(testfile('success_simple_expr_or_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'false',
'character' => 'false' }
token = parser.parse(line.chomp)
line = 'a_girl_has_no_name OR character'
parser.names = {
'a_girl_has_no_name' => 'false',
'character' => 'false'
}
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate true AND true and return true' do
line = File
.read(testfile('success_simple_expr_and_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'true',
'character' => 'true' }
token = parser.parse(line.chomp)
line = 'a_girl_has_no_name AND character'
parser.names = {
'a_girl_has_no_name' => 'true',
'character' => 'true'
}
token = parser.parse(line)
assert_equal(true, token.compute)
end
it 'has to evaluate true AND false and return false' do
line = File
.read(testfile('success_simple_expr_and_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'true',
'character' => 'false' }
token = parser.parse(line.chomp)
line = 'a_girl_has_no_name AND character'
parser.names = {
'a_girl_has_no_name' => 'true',
'character' => 'false'
}
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate false AND true and return false' do
line = File
.read(testfile('success_simple_expr_and_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'false',
'character' => 'true' }
token = parser.parse(line.chomp)
line = 'a_girl_has_no_name AND character'
parser.names = {
'a_girl_has_no_name' => 'false',
'character' => 'true'
}
token = parser.parse(line)
assert_equal(false, token.compute)
end
it 'has to evaluate false AND false and return false' do
line = File
.read(testfile('success_simple_expr_and_expr.txt')).delete("\n")
parser.names = { 'a_girl_has_no_name' => 'false',
'character' => 'false' }
token = parser.parse(line.chomp)
line = 'a_girl_has_no_name AND character'
parser.names = {
'a_girl_has_no_name' => 'false',
'character' => 'false'
}
token = parser.parse(line)
assert_equal(false, token.compute)
end
end