logic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Bryan Ash
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,42 @@
1
+ = logic
2
+
3
+ == Usage
4
+
5
+ Truth tables are easy:
6
+
7
+ logic truth_table '(a or b) and (c or d)'
8
+
9
+ Gives you:
10
+
11
+ a b c d | output
12
+ 1) 0 0 0 0 | 0
13
+ 2) 0 0 0 1 | 0
14
+ 3) 0 0 1 0 | 0
15
+ 4) 0 0 1 1 | 0
16
+ 5) 0 1 0 0 | 0
17
+ 6) 0 1 0 1 | 1
18
+ 7) 0 1 1 0 | 1
19
+ 8) 0 1 1 1 | 1
20
+ 9) 1 0 0 0 | 0
21
+ 10) 1 0 0 1 | 1
22
+ 11) 1 0 1 0 | 1
23
+ 12) 1 0 1 1 | 1
24
+ 13) 1 1 0 0 | 0
25
+ 14) 1 1 0 1 | 1
26
+ 15) 1 1 1 0 | 1
27
+ 16) 1 1 1 1 | 1
28
+
29
+ MC/DC test case pairs can also be found:
30
+
31
+ logic mcdc_pairs '(a or b) and (c or d)'
32
+
33
+ Gives you:
34
+
35
+ a => [[2, 10], [3, 11], [4, 12]]
36
+ b => [[2, 6], [3, 7], [4, 8]]
37
+ c => [[5, 7], [9, 11], [13, 15]]
38
+ d => [[5, 6], [9, 10], [13, 14]]
39
+
40
+ == Copyright
41
+
42
+ Copyright (c) 2010 Bryan Ash. See LICENSE for details.
data/bin/logic ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/lib")
3
+
4
+ require 'rubygems'
5
+ require 'treetop'
6
+ require 'logic_parser'
7
+ require 'logic_operations'
8
+
9
+ parser = LogicParser.new
10
+ decision = parser.parse ARGV[1]
11
+
12
+ if ARGV[0] == 'truth_table'
13
+ puts decision.truth_table
14
+ end
15
+
16
+ if ARGV[0] == 'mcdc_pairs'
17
+ puts decision.mcdc_pairs
18
+ end
@@ -0,0 +1,37 @@
1
+ Feature: MC/DC Pairs
2
+
3
+ In order to reduce the number of tests required for a logic statment
4
+ as an engineer
5
+ I want to see the MC/DC test pairs
6
+
7
+ Scenario: a single condition
8
+ When I run logic mcdc_pairs 'a'
9
+ Then I should see:
10
+ """
11
+ a => [[1, 2]]
12
+ """
13
+
14
+ Scenario: 'a and b' decision
15
+ When I run logic mcdc_pairs 'a and b'
16
+ Then I should see:
17
+ """
18
+ a => [[2, 4]]
19
+ b => [[3, 4]]
20
+ """
21
+
22
+ Scenario: 'a or b' decision
23
+ When I run logic mcdc_pairs 'a or b'
24
+ Then I should see:
25
+ """
26
+ a => [[1, 3]]
27
+ b => [[1, 2]]
28
+ """
29
+
30
+ Scenario: 'a and (b or c)' decision
31
+ When I run logic mcdc_pairs 'a and (b or c)'
32
+ Then I should see:
33
+ """
34
+ a => [[2, 6], [3, 7], [4, 8]]
35
+ b => [[5, 7]]
36
+ c => [[5, 6]]
37
+ """
@@ -0,0 +1,3 @@
1
+ When /^I run (.+) (.+) '(.+)'$/ do |ruby_file, command, decision|
2
+ When "I run \"ruby #{APP_PATH}/#{ruby_file}.rb #{command} '#{decision}'\""
3
+ end
@@ -0,0 +1,7 @@
1
+ APP_PATH = File.expand_path(File.dirname(__FILE__) + '/../..')
2
+
3
+ $LOAD_PATH.unshift("#{APP_PATH}/lib")
4
+
5
+ require 'aruba'
6
+ require 'fileutils'
7
+ require 'spec/expectations'
@@ -0,0 +1,70 @@
1
+ Feature: Truth Table
2
+
3
+ In order to appreciate the complexity of a logic statement
4
+ as an engineer
5
+ I want to see the truth table
6
+
7
+ Scenario: a single condition
8
+ When I run logic truth_table 'a'
9
+ Then I should see:
10
+ """
11
+ a | output
12
+ 1) 0 | 0
13
+ 2) 1 | 1
14
+ """
15
+
16
+ Scenario: 'a or b' decision
17
+ When I run logic truth_table 'a or b'
18
+ Then I should see:
19
+ """
20
+ a b | output
21
+ 1) 0 0 | 0
22
+ 2) 0 1 | 1
23
+ 3) 1 0 | 1
24
+ 4) 1 1 | 1
25
+ """
26
+
27
+ Scenario: 'a or b or c' decision
28
+ When I run logic truth_table 'a or b or c'
29
+ Then I should see:
30
+ """
31
+ a b c | output
32
+ 1) 0 0 0 | 0
33
+ 2) 0 0 1 | 1
34
+ 3) 0 1 0 | 1
35
+ 4) 0 1 1 | 1
36
+ 5) 1 0 0 | 1
37
+ 6) 1 0 1 | 1
38
+ 7) 1 1 0 | 1
39
+ 8) 1 1 1 | 1
40
+ """
41
+
42
+ Scenario: 'a and b and c' decision
43
+ When I run logic truth_table 'a and b and c'
44
+ Then I should see:
45
+ """
46
+ a b c | output
47
+ 1) 0 0 0 | 0
48
+ 2) 0 0 1 | 0
49
+ 3) 0 1 0 | 0
50
+ 4) 0 1 1 | 0
51
+ 5) 1 0 0 | 0
52
+ 6) 1 0 1 | 0
53
+ 7) 1 1 0 | 0
54
+ 8) 1 1 1 | 1
55
+ """
56
+
57
+ Scenario: '(a and b) or c' decision
58
+ When I run logic truth_table '(a and b) or c'
59
+ Then I should see:
60
+ """
61
+ a b c | output
62
+ 1) 0 0 0 | 0
63
+ 2) 0 0 1 | 1
64
+ 3) 0 1 0 | 0
65
+ 4) 0 1 1 | 1
66
+ 5) 1 0 0 | 0
67
+ 6) 1 0 1 | 1
68
+ 7) 1 1 0 | 1
69
+ 8) 1 1 1 | 1
70
+ """
data/lib/integer.rb ADDED
@@ -0,0 +1,11 @@
1
+ class Integer
2
+
3
+ def to_array_of_bits(width)
4
+ ("%0#{width}b" % self).split(//).map(&:to_i)
5
+ end
6
+
7
+ def negate
8
+ self + 1 & 1
9
+ end
10
+
11
+ end
data/lib/logic.rb ADDED
@@ -0,0 +1,3 @@
1
+ class Logic
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,69 @@
1
+ require 'truth_table'
2
+
3
+ module LogicStatement
4
+
5
+ def truth_table
6
+ TruthTable.new(condition_count) { |conditions| evaluate(conditions) }
7
+ end
8
+
9
+ def mcdc_pairs
10
+ truth_table.mcdc_pairs
11
+ end
12
+
13
+ def condition_count
14
+ condition_identifiers.count
15
+ end
16
+
17
+ end
18
+
19
+ module Condition
20
+
21
+ include LogicStatement
22
+
23
+ def condition_identifiers
24
+ [text_value]
25
+ end
26
+
27
+ def evaluate(conditions)
28
+ conditions.shift
29
+ end
30
+
31
+ end
32
+
33
+ module Decision
34
+
35
+ include LogicStatement
36
+
37
+ def condition_identifiers
38
+ elements.map(&:condition_identifiers).flatten
39
+ end
40
+
41
+ def evaluate(conditions)
42
+ condition_1 = operand_1.evaluate(conditions)
43
+ condition_2 = operand_2.evaluate(conditions)
44
+ operator.apply(condition_1, condition_2)
45
+ end
46
+
47
+ end
48
+
49
+ module Parenthesized
50
+
51
+ include LogicStatement
52
+
53
+ def condition_identifiers
54
+ binary_condition.condition_identifiers
55
+ end
56
+
57
+ def evaluate(conditions)
58
+ binary_condition.evaluate(conditions)
59
+ end
60
+
61
+ end
62
+
63
+ module NonCondition
64
+
65
+ def condition_identifiers
66
+ []
67
+ end
68
+
69
+ end
@@ -0,0 +1,51 @@
1
+ grammar Logic
2
+
3
+ rule decision
4
+ binary_condition / condition
5
+ end
6
+
7
+ rule binary_condition
8
+ operand_1:condition S operator S operand_2:decision <Decision>
9
+ end
10
+
11
+ rule condition
12
+ parenthesized / variable
13
+ end
14
+
15
+ rule variable
16
+ [A-Za-z]+ <Condition>
17
+ end
18
+
19
+ rule parenthesized
20
+ '(' binary_condition ')' <Parenthesized>
21
+ end
22
+
23
+ rule operator
24
+ ( and / or ) <NonCondition>
25
+ end
26
+
27
+ rule and
28
+ 'and' {
29
+ def apply(a, b)
30
+ a & b
31
+ end
32
+ }
33
+ end
34
+
35
+ rule or
36
+ 'or' {
37
+ def apply(a, b)
38
+ a | b
39
+ end
40
+ }
41
+ end
42
+
43
+ rule s # Optional space
44
+ S?
45
+ end
46
+
47
+ rule S # Mandatory space
48
+ [ \t\n\r]+ <NonCondition>
49
+ end
50
+
51
+ end
data/lib/test_case.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'integer'
2
+
3
+ class TestCase
4
+
5
+ def initialize(number, condition_count, decision)
6
+ @condition_value = number - 1
7
+ @condition_count = condition_count
8
+ @decision = decision
9
+ end
10
+
11
+ def initialize_copy(source)
12
+ super
13
+ @conditions = conditions.dup
14
+ end
15
+
16
+ def conditions
17
+ @conditions ||= (@condition_value).to_array_of_bits(@condition_count)
18
+ end
19
+
20
+ def number
21
+ conditions.join.to_i(base = 2) + 1
22
+ end
23
+
24
+ def output
25
+ @decision.call(conditions.dup)
26
+ end
27
+
28
+ def mcdc_pair(index)
29
+ modified_case = negate_condition_at_index(index)
30
+ [number, modified_case.number].sort
31
+ end
32
+
33
+ def is_mcdc_case_for_index?(index)
34
+ modified_case = negate_condition_at_index(index)
35
+ modified_case.output != output
36
+ end
37
+
38
+ def negate_condition_at_index(index)
39
+ dup.negate_condition_at_index!(index)
40
+ end
41
+
42
+ def negate_condition_at_index!(index)
43
+ conditions[index] = conditions[index].negate
44
+ self
45
+ end
46
+
47
+ end
@@ -0,0 +1,61 @@
1
+ require 'test_case'
2
+
3
+ class TruthTable
4
+
5
+ def initialize(condition_count, &decision)
6
+ @condition_count = condition_count
7
+ @decision = decision
8
+ end
9
+
10
+ def condition_identifiers
11
+ identifier_range.take(@condition_count)
12
+ end
13
+
14
+ def cases
15
+ case_numbers.map do |case_number|
16
+ TestCase.new(case_number, @condition_count, @decision)
17
+ end
18
+ end
19
+
20
+ def case_numbers
21
+ 1..2**@condition_count
22
+ end
23
+
24
+ def mcdc_pairs
25
+ condition_identifiers.map do |condition_identifier|
26
+ "#{condition_identifier} => #{mcdc_pairs_for(condition_identifier)}"
27
+ end
28
+ end
29
+
30
+ def mcdc_pairs_for(condition_identifier)
31
+ cases.inject([]) do |mcdc_cases, test_case|
32
+ mcdc_pair = test_case.mcdc_pair(index_of(condition_identifier))
33
+ mcdc_cases << mcdc_pair if test_case.is_mcdc_case_for_index?(index_of(condition_identifier))
34
+ mcdc_cases
35
+ end.uniq
36
+ end
37
+
38
+ def index_of(condition_identifier)
39
+ identifier_range.to_a.index(condition_identifier)
40
+ end
41
+
42
+ def to_s
43
+ header + formatted_cases
44
+ end
45
+
46
+ def header
47
+ conditions = condition_identifiers.join(" ")
48
+ "#{conditions.rjust(conditions.length + 4)} | output\n"
49
+ end
50
+
51
+ def formatted_cases
52
+ cases.inject("") do |output, test_case|
53
+ output += "#{test_case.number.to_s.rjust(2)}) #{test_case.conditions.join(' ')} | #{test_case.output.to_s.rjust(3)}\n"
54
+ end
55
+ end
56
+
57
+ def identifier_range
58
+ 'a'..'z'
59
+ end
60
+
61
+ end
@@ -0,0 +1,132 @@
1
+ require 'rubygems'
2
+ require 'treetop'
3
+ require 'logic_parser'
4
+ require 'logic_operations'
5
+
6
+ describe LogicParser do
7
+
8
+ let(:parser) { LogicParser.new }
9
+
10
+ describe "parsing a single condition" do
11
+
12
+ let(:decision) { parser.parse('Hello') }
13
+
14
+ it "identifies the condition" do
15
+ decision.condition_identifiers.should == ['Hello']
16
+ end
17
+
18
+ it "has 1 condition" do
19
+ decision.condition_count.should == 1
20
+ end
21
+
22
+ end
23
+
24
+ describe "parsing 'A or B'" do
25
+
26
+ let(:decision) { parser.parse('A or B') }
27
+
28
+ it "condition identifiers are ['A','B']" do
29
+ decision.condition_identifiers.should == ['A','B']
30
+ end
31
+
32
+ it "has 2 conditions" do
33
+ decision.condition_count.should == 2
34
+ end
35
+
36
+ end
37
+
38
+ describe "parsing 'A or B or C'" do
39
+
40
+ let(:decision) { parser.parse('A or B or C') }
41
+
42
+ it "condition identifiers are ['A','B','C']" do
43
+ decision.condition_identifiers.should == ['A','B', 'C']
44
+ end
45
+
46
+ it "has 3 conditions" do
47
+ decision.condition_count.should == 3
48
+ end
49
+
50
+ end
51
+
52
+ describe "parsing 'A and B'" do
53
+
54
+ let(:decision) { parser.parse('A and B') }
55
+
56
+ it "conditions are ['A','B']" do
57
+ decision.condition_identifiers.should == ['A','B']
58
+ end
59
+
60
+ it "has 2 conditions" do
61
+ decision.condition_count.should == 2
62
+ end
63
+
64
+ end
65
+
66
+ describe "parsing 'A and B and C'" do
67
+
68
+ let(:decision) { parser.parse('A and B and C') }
69
+
70
+ it "condition identifiers are ['A','B','C']" do
71
+ decision.condition_identifiers.should == ['A','B', 'C']
72
+ end
73
+
74
+ it "has 3 conditions" do
75
+ decision.condition_count.should == 3
76
+ end
77
+
78
+ end
79
+
80
+ { 'Hello' =>
81
+ { [0] => 0,
82
+ [1] => 1
83
+ },
84
+ 'A or B' =>
85
+ { [0, 0] => 0,
86
+ [0, 1] => 1,
87
+ [1, 0] => 1
88
+ },
89
+ 'A and B' =>
90
+ { [1, 1] => 1,
91
+ [0, 1] => 0,
92
+ [1, 0] => 0
93
+ },
94
+ 'A or B or C' =>
95
+ { [0, 0, 0] => 0,
96
+ [1, 0, 0] => 1,
97
+ [0, 1, 0] => 1,
98
+ [0, 0, 1] => 1
99
+ },
100
+ 'A and B and C' =>
101
+ { [1, 1, 1] => 1,
102
+ [0, 1, 1] => 0,
103
+ [1, 0, 1] => 0,
104
+ [1, 1, 0] => 0
105
+ },
106
+ '(A and B) or C' =>
107
+ { [1, 1, 0] => 1,
108
+ [0, 1, 0] => 0,
109
+ [1, 0, 0] => 0,
110
+ [0, 0, 1] => 1
111
+ },
112
+ 'A and (B or C)' =>
113
+ { [1, 0, 0] => 0,
114
+ [1, 1, 0] => 1,
115
+ [1, 0, 1] => 1,
116
+ [0, 1, 1] => 0
117
+ }
118
+ }.each do |logic, cases|
119
+ describe "parsing '#{logic}'" do
120
+
121
+ let(:decision) { parser.parse(logic) }
122
+
123
+ cases.each do |conditions, result|
124
+ it "'#{conditions}' evaluates to '#{result}'" do
125
+ decision.evaluate(conditions).should == result
126
+ end
127
+ end
128
+ end
129
+
130
+ end
131
+
132
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,27 @@
1
+ require 'truth_table'
2
+
3
+ describe TruthTable do
4
+
5
+ describe "with 1 condition" do
6
+
7
+ let(:table) { TruthTable.new(1) }
8
+
9
+ describe :condition_dentifiers do
10
+
11
+ it "includes 'a'" do
12
+ table.condition_identifiers.should include('a')
13
+ end
14
+
15
+ end
16
+
17
+ describe :cases do
18
+
19
+ it "has 2 cases" do
20
+ table.cases.length.should == 2
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logic
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Bryan Ash
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-18 00:00:00 -04:00
18
+ default_executable: logic
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: treetop
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 4
30
+ - 2
31
+ version: 1.4.2
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: polyglot
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 2
44
+ - 9
45
+ version: 0.2.9
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Produces truth table and MC/DC test case pairs from parsed logic statement
49
+ email: ""
50
+ executables:
51
+ - logic
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - LICENSE
56
+ - README.rdoc
57
+ files:
58
+ - bin/logic
59
+ - features/mcdc_pairs.feature
60
+ - features/step_definitions/logic_testing_steps.rb
61
+ - features/support/env.rb
62
+ - features/truth_table.feature
63
+ - lib/integer.rb
64
+ - lib/logic.rb
65
+ - lib/logic_operations.rb
66
+ - lib/logic_parser.treetop
67
+ - lib/test_case.rb
68
+ - lib/truth_table.rb
69
+ - LICENSE
70
+ - README.rdoc
71
+ - spec/logic_parser_spec.rb
72
+ - spec/spec.opts
73
+ - spec/truth_table_spec.rb
74
+ has_rdoc: true
75
+ homepage: ""
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.6
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Taking the pain out of MC/DC testing
104
+ test_files: []
105
+