pairwise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ tmp/
3
+ pkg
4
+ rdoc
5
+ .#*
6
+ coverage/
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Joseph Wilk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pairwise"
8
+ gem.summary = %Q{Turn large test data combinations into smaller ones}
9
+ gem.description = %Q{A tool for selecting a smaller number of test combinations (using pairwise generation) rather than exhaustively testing all possible permutations.}
10
+ gem.email = "joe@josephwilk.net"
11
+ gem.homepage = "http://wiki.github.com/josephwilk/pairwise"
12
+ gem.authors = ["Joseph Wilk"]
13
+
14
+ gem.add_development_dependency 'rspec'
15
+ gem.add_development_dependency 'cucumber'
16
+ end
17
+
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ Dir['gem_tasks/**/*.rake'].each { |rake| load rake }
24
+
25
+ task :default => ["spec", "features"]
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 1
data/bin/pairwise ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'pairwise'
5
+ require 'pairwise/cli'
6
+
7
+ Pairwise::Cli.execute(ARGV)
@@ -0,0 +1,80 @@
1
+ Feature: Generating pairwise data
2
+ In order to test small, managable datasets while having confidence in test coverage
3
+ As a tester
4
+ I want a set of tests which is smaller than all the possible combinations of my specified inputs
5
+
6
+ Scenario: No input file specified
7
+ When I run pairwise
8
+ Then I should see in the output
9
+ """
10
+ Usage: pairwise [options] FILE.yml
11
+ """
12
+
13
+ Scenario: Ordered yaml inputs
14
+ Given I have the yaml file "inputs.yml" containing:
15
+ """
16
+ - event with image: [Football, Basketball, Soccer]
17
+ - event without image: [Football, Basketball, Soccer]
18
+ - media: [Image, Video, Music]
19
+ """
20
+ When I run pairwise inputs.yml
21
+ Then I should see the output
22
+ """
23
+ | event with image | event without image | media |
24
+ | Football | Football | Image |
25
+ | Football | Basketball | Video |
26
+ | Football | Soccer | Music |
27
+ | Basketball | Football | Music |
28
+ | Basketball | Basketball | Image |
29
+ | Basketball | Soccer | Video |
30
+ | Soccer | Football | Video |
31
+ | Soccer | Basketball | Music |
32
+ | Soccer | Soccer | Image |
33
+
34
+ """
35
+
36
+ Scenario: Unorderd yaml inputs
37
+ Given I have the yaml file "inputs.yml" containing:
38
+ """
39
+ event with image: [Football, Basketball, Soccer]
40
+ event without image: [Football, Basketball, Soccer]
41
+ media: [Image, Video, Music]
42
+ """
43
+ When I run pairwise inputs.yml
44
+ Then I should see the output
45
+ """
46
+ | media | event without image | event with image |
47
+ | Image | Football | Football |
48
+ | Image | Basketball | Basketball |
49
+ | Image | Soccer | Soccer |
50
+ | Video | Football | Soccer |
51
+ | Video | Basketball | Football |
52
+ | Video | Soccer | Basketball |
53
+ | Music | Football | Basketball |
54
+ | Music | Basketball | Soccer |
55
+ | Music | Soccer | Football |
56
+
57
+ """
58
+
59
+ Scenario: Not replacing wild cards
60
+ Given I have the yaml file "inputs.yml" containing:
61
+ """
62
+ - A: [A1, A2, A3]
63
+ - B: [B1, B2]
64
+ - C: [C1, C2, C3]
65
+ """
66
+ When I run pairwise inputs.yml --keep-wild-cards
67
+ Then I should see the output
68
+ """
69
+ | A | B | C |
70
+ | A1 | B1 | C1 |
71
+ | A1 | B2 | C2 |
72
+ | A2 | B1 | C3 |
73
+ | A2 | B2 | C1 |
74
+ | A3 | B1 | C2 |
75
+ | A3 | B2 | C3 |
76
+ | A3 | wild_card | C1 |
77
+ | A2 | wild_card | C2 |
78
+ | A1 | wild_card | C3 |
79
+
80
+ """
@@ -0,0 +1,15 @@
1
+ Given /^I have the yaml file "([^\"]*)" containing:$/ do |file_name, file_contents|
2
+ create_file(file_name, file_contents)
3
+ end
4
+
5
+ When /^I run (.+)$/ do |command|
6
+ run(command)
7
+ end
8
+
9
+ Then /^I should see the output$/ do |text|
10
+ last_stdout.should == text
11
+ end
12
+
13
+ Then /^I should see in the output$/ do |string|
14
+ last_stdout.should include(string)
15
+ end
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'tempfile'
3
+ require 'spec/expectations'
4
+ require "spec/mocks"
5
+ require 'fileutils'
6
+ require 'forwardable'
7
+
8
+ SCRATCH_SPACE = 'tmp'
9
+
10
+ class PairwiseWorld
11
+ extend Forwardable
12
+ def_delegators PairwiseWorld, :self_test_dir
13
+
14
+ def self.examples_dir(subdir=nil)
15
+ @examples_dir ||= File.expand_path(File.join(File.dirname(__FILE__), "../../#{SCRATCH_SPACE}"))
16
+ subdir ? File.join(@examples_dir, subdir) : @examples_dir
17
+ end
18
+
19
+ def self.self_test_dir
20
+ @self_test_dir ||= examples_dir
21
+ end
22
+
23
+ def pairwise_lib_dir
24
+ @pairwise_lib_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))
25
+ end
26
+
27
+ def initialize
28
+ @current_dir = self_test_dir
29
+ end
30
+
31
+ private
32
+ attr_reader :last_exit_status, :last_stderr
33
+
34
+ def last_stdout
35
+ @last_stdout
36
+ end
37
+
38
+ def create_file(file_name, file_content)
39
+ in_current_dir do
40
+ FileUtils.mkdir_p(File.dirname(file_name)) unless File.directory?(File.dirname(file_name))
41
+ File.open(file_name, 'w') { |f| f << file_content }
42
+ end
43
+ end
44
+
45
+ def set_env_var(variable, value)
46
+ @original_env_vars ||= {}
47
+ @original_env_vars[variable] = ENV[variable]
48
+ ENV[variable] = value
49
+ end
50
+
51
+ def in_current_dir(&block)
52
+ Dir.chdir(@current_dir, &block)
53
+ end
54
+
55
+ def run(command)
56
+ stderr_file = Tempfile.new('pairwise')
57
+ stderr_file.close
58
+ in_current_dir do
59
+ IO.popen("../bin/#{command} 2> #{stderr_file.path}", 'r') do |io|
60
+ @last_stdout = io.read
61
+ end
62
+
63
+ @last_exit_status = $?.exitstatus
64
+ end
65
+ @last_stderr = IO.read(stderr_file.path)
66
+ end
67
+ end
68
+
69
+ World do
70
+ PairwiseWorld.new
71
+ end
72
+
73
+ Before do
74
+ FileUtils.rm_rf SCRATCH_SPACE
75
+ FileUtils.mkdir SCRATCH_SPACE
76
+ end
@@ -0,0 +1,4 @@
1
+ Before do
2
+ Kernel.stub!(:rand).and_return(0)
3
+ end
4
+
@@ -0,0 +1,10 @@
1
+ require 'cucumber'
2
+ require 'cucumber/rake/task'
3
+
4
+ Cucumber::Rake::Task.new('features') do |t|
5
+ t.rcov = ENV['RCOV']
6
+ end
7
+
8
+ Cucumber::Rake::Task.new('pretty') do |t|
9
+ t.cucumber_opts = %w{--format pretty}
10
+ end
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'spec/expectations'
3
+ require 'spec/rake/spectask'
4
+
5
+ desc "Run RSpec"
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.spec_opts = ['--options', "spec/spec.opts"]
8
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
+ t.rcov = ENV['RCOV']
10
+ t.rcov_opts = %w{--exclude gems\/,spec\/}
11
+ t.verbose = true
12
+ end
13
+ rescue LoadError
14
+ task :spec
15
+ end
data/lib/pairwise.rb ADDED
@@ -0,0 +1,35 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'pairwise/test_pair'
5
+ require 'pairwise/builder'
6
+ require 'pairwise/formatter'
7
+ require 'pairwise/cli'
8
+
9
+ require 'yaml'
10
+
11
+ module Pairwise
12
+ class InvalidInputData < Exception; end
13
+
14
+ version = YAML.load_file(File.dirname(__FILE__) + '/../VERSION.yml')
15
+ VERSION = [version[:major], version[:minor], version[:patch], version[:build]].compact.join('.')
16
+
17
+ class << self
18
+ def combinations(*inputs)
19
+ raise InvalidInputData, "Minimum of 2 inputs are required to generate pairwise test set" unless valid?(inputs)
20
+ Pairwise::Builder.new(inputs).build
21
+ end
22
+
23
+ private
24
+ def valid?(inputs)
25
+ array_of_arrays?(inputs) &&
26
+ inputs.length >= 2 &&
27
+ !inputs[0].empty? && !inputs[1].empty?
28
+ end
29
+
30
+ def array_of_arrays?(data)
31
+ data.reject{|datum| datum.kind_of?(Array)}.empty?
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,142 @@
1
+ # A pairwise implementation using the in-parameter-order (IPO) strategy.
2
+ # Based on: http://ranger.uta.edu/~ylei/paper/ipo-tse.pdf
3
+ module Pairwise
4
+ class Builder
5
+
6
+ WILD_CARD = 'wild_card'
7
+
8
+ def initialize(inputs, options = {})
9
+ @inputs = inputs
10
+ @options = options
11
+ end
12
+
13
+ def build
14
+ input_lists = generate_pairs_between(@inputs[0], [@inputs[1]], 0)
15
+ @inputs.size > 2 ? in_parameter_order_generation(input_lists) : input_lists.map{|list| list.to_a}
16
+ end
17
+
18
+ private
19
+
20
+ def in_parameter_order_generation(input_lists)
21
+ @inputs[2..-1].each_with_index do |input_list, i|
22
+ i += 2
23
+ input_lists, pi = horizontal_growth(input_lists, input_list, @inputs[0..(i-1)])
24
+ input_lists = vertical_growth(input_lists, pi)
25
+ end
26
+ input_lists = replace_redundant_wild_cards(input_lists)
27
+ input_lists = replace_wild_cards(input_lists) unless @options[:keep_wild_cards]
28
+ input_lists
29
+ end
30
+
31
+ def horizontal_growth(input_lists, parameter_i, inputs)
32
+ pi = generate_pairs_between(parameter_i, inputs, inputs.size)
33
+
34
+ if input_lists.size <= parameter_i.size
35
+ input_lists = input_lists.enum_for(:each_with_index).map do |input_list, index_of_input_under_inspection|
36
+ extended_input_list = input_list + [parameter_i[index_of_input_under_inspection]]
37
+ pi = remove_pairs_covered_by(extended_input_list, pi)
38
+ extended_input_list
39
+ end
40
+ else
41
+ input_lists[0...parameter_i.size] = input_lists[0...parameter_i.size].enum_for(:each_with_index).map do |input_list, index_of_input_under_inspection|
42
+ extended_input_list = input_list + [parameter_i[index_of_input_under_inspection]]
43
+ pi = remove_pairs_covered_by(extended_input_list, pi)
44
+ extended_input_list
45
+ end
46
+
47
+ input_lists[parameter_i.size..-1] = input_lists[parameter_i.size..-1].map do |input_list|
48
+ extended_input_list = input_list_that_covers_most_pairs(input_list, parameter_i, pi)
49
+ pi = remove_pairs_covered_by(extended_input_list, pi)
50
+ extended_input_list
51
+ end
52
+ end
53
+
54
+ [input_lists, pi]
55
+ end
56
+
57
+ def vertical_growth(input_lists, pi)
58
+ new_input_lists = []
59
+
60
+ pi.each do |uncovered_pair|
61
+ #TODO: Decided if we should replace all matches or single matches?
62
+ if test_position = uncovered_pair.replaceable_wild_card?(new_input_lists)
63
+ new_input_lists[test_position] = uncovered_pair.replace_wild_card(new_input_lists[test_position])
64
+ else
65
+ new_input_lists << uncovered_pair.create_input_list
66
+ end
67
+ end
68
+
69
+ input_lists + new_input_lists
70
+ end
71
+
72
+ def replace_redundant_wild_cards(input_lists)
73
+ map_each_input_value(input_lists) do |input_value, index|
74
+ if input_value == WILD_CARD && @inputs[index].length == 1
75
+ @inputs[index][0]
76
+ else
77
+ input_value
78
+ end
79
+ end
80
+ end
81
+
82
+ def replace_wild_cards(input_lists)
83
+ map_each_input_value(input_lists) do |input_value, index|
84
+ if input_value == WILD_CARD
85
+ pick_random_value(@inputs[index])
86
+ else
87
+ input_value
88
+ end
89
+ end
90
+ end
91
+
92
+ def map_each_input_value(input_lists)
93
+ input_lists.map do |input_list|
94
+ input_list.enum_for(:each_with_index).map do |input_value, index|
95
+ yield input_value, index
96
+ end
97
+ end
98
+ end
99
+
100
+ def pick_random_value(input_list)
101
+ input_list[rand(input_list.size)]
102
+ end
103
+
104
+ def generate_pairs_between(parameter_i, input_lists, p_index)
105
+ pairs = []
106
+ parameter_i.each do |p|
107
+ input_lists.each_with_index do |input_list, q_index|
108
+ input_list.each do |q|
109
+ pairs << TestPair.new(p_index, q_index, p, q)
110
+ end
111
+ end
112
+ end
113
+ pairs
114
+ end
115
+
116
+ def remove_pairs_covered_by(extended_input_list, pi)
117
+ pi.reject{|pair| pair.covered_by?(extended_input_list)}
118
+ end
119
+
120
+ def input_list_that_covers_most_pairs(input_list, parameter_i, pi)
121
+ selected_input_list = nil
122
+ parameter_i.reduce(0) do |max_covered_count, value|
123
+ input_list_candidate = input_list + [value]
124
+ covered_count = pairs_covered_count(input_list_candidate, pi)
125
+ if covered_count >= max_covered_count
126
+ selected_input_list = input_list_candidate
127
+ covered_count
128
+ else
129
+ max_covered_count
130
+ end
131
+ end
132
+ selected_input_list
133
+ end
134
+
135
+ def pairs_covered_count(input_list, pairs)
136
+ pairs.reduce(0) do |covered_count, pair|
137
+ covered_count += 1 if pair.covered_by?(input_list)
138
+ covered_count
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,78 @@
1
+ require 'yaml'
2
+ require 'optparse'
3
+ module Pairwise
4
+ class Cli
5
+ class << self
6
+ def execute(args)
7
+ new(args).execute!
8
+ end
9
+ end
10
+
11
+ def initialize(args, out = STDOUT)
12
+ @args, @out = args, out
13
+ @formatter = Formatter::Cucumber.new(@out)
14
+ @options = defaults
15
+ end
16
+
17
+ def parse!
18
+ @args.extend(::OptionParser::Arguable)
19
+ @args.options do |opts|
20
+ opts.banner = ["Usage: pairwise [options] FILE.yml", "",
21
+ "Example:",
22
+ "pairwise data/inputs.yml", "", "",
23
+ ].join("\n")
24
+ opts.on("-k", "--keep-wild-cards") do
25
+ @options[:keep_wild_cards] = true
26
+ end
27
+ opts.on_tail("--version", "Show version.") do
28
+ @out.puts Pairwise::VERSION
29
+ Kernel.exit(0)
30
+ end
31
+ opts.on_tail("-h", "--help", "You're looking at it.") do
32
+ exit_with_help
33
+ end
34
+ end.parse!
35
+
36
+ @input_file = @args[0] unless @args.empty?
37
+ end
38
+
39
+ def execute!
40
+ parse!
41
+ exit_with_help if @input_file.nil? || @input_file.empty?
42
+ input_data, input_labels = *load_and_parse_input_file!
43
+
44
+ builder = Pairwise::Builder.new(input_data, @options)
45
+
46
+ @formatter.display(builder.build, input_labels)
47
+ end
48
+
49
+ private
50
+ def defaults
51
+ {:keep_wild_cards => false}
52
+ end
53
+
54
+ def exit_with_help
55
+ @out.puts @args.options.help
56
+ Kernel.exit(0)
57
+ end
58
+
59
+ def load_and_parse_input_file!
60
+ inputs = YAML.load_file(@input_file)
61
+ inputs = hash_inputs_to_list(inputs) if inputs.is_a?(Hash)
62
+
63
+ raw_inputs = inputs.map {|input| input.values[0]}
64
+ input_labels = input_names(inputs)
65
+
66
+ [raw_inputs, input_labels]
67
+ end
68
+
69
+ def hash_inputs_to_list(inputs_hash)
70
+ inputs_hash.map {|key, value| {key => value}}
71
+ end
72
+
73
+ def input_names(inputs)
74
+ inputs.map{|input| input.keys}.flatten
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1 @@
1
+ %q[cucumber].each {|file| require "pairwise/formatter/#{file}"}
@@ -0,0 +1,40 @@
1
+ module Pairwise
2
+ module Formatter
3
+ class Cucumber
4
+
5
+ def initialize(out)
6
+ @out = out
7
+ @max = {}
8
+ end
9
+
10
+ def display(test_data, inputs)
11
+ @test_data, @inputs = test_data, inputs
12
+
13
+ @out.print "|"
14
+ inputs.each_with_index do |key, column|
15
+ @out.print padded_string(key, column) + "|"
16
+ end
17
+ @out.puts
18
+
19
+ test_data.each do |data|
20
+ @out.print "|"
21
+ data.each_with_index do |datum, column|
22
+ @out.print padded_string(datum, column) + "|"
23
+ end
24
+ @out.puts
25
+ end
26
+ end
27
+
28
+ private
29
+ def padded_string(string, column)
30
+ padding_length = max_line_length(column) - string.length
31
+ " #{string} " + (" " * padding_length)
32
+ end
33
+
34
+ def max_line_length(column)
35
+ @max[column] ||= ([@inputs[column].length] + @test_data.map{|data| data[column].length}).max
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ require 'forwardable'
2
+
3
+ module Pairwise
4
+ class TestPair
5
+ extend Forwardable
6
+
7
+ def_delegators :@pair, :+, :inspect, :to_a, :==
8
+
9
+ def initialize(p1_position, p2_position, p1, p2)
10
+ @p1, @p2 = p1, p2
11
+ @p1_position, @p2_position = p1_position, p2_position
12
+ @pair = [@p1, @p2]
13
+ end
14
+
15
+ def covered_by?(test_pair)
16
+ test_pair[@p1_position] == @p1 &&
17
+ test_pair[@p2_position] == @p2
18
+ end
19
+
20
+ def create_input_list
21
+ new_input_list = Array.new(@p1_position){Builder::WILD_CARD}
22
+
23
+ new_input_list[@p1_position] = @p1
24
+ new_input_list[@p2_position] = @p2
25
+ new_input_list
26
+ end
27
+
28
+ def replace_wild_card(input_list)
29
+ input_list[@p2_position] = @p2
30
+ input_list
31
+ end
32
+
33
+ def replaceable_wild_card?(input_lists)
34
+ wild_card_list = input_lists.map do |input_list|
35
+ input_list[@p2_position] == Builder::WILD_CARD && input_list[@p1_position] == @p1
36
+ end
37
+ wild_card_list.rindex(true)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Pairwise
4
+ describe Builder do
5
+
6
+ describe 'ipo horizontal growth' do
7
+ before(:each) do
8
+ @test_pairs = [[:A1, :B1], [:A1, :B2], [:A2, :B1], [:A2, :B2]]
9
+ @data = [[:A1, :A2],[:B1, :B2],[:C1 , :C2 , :C3 ]]
10
+
11
+ @builder = Builder.new(@test_pairs)
12
+ end
13
+
14
+ it "should return pairs extended with C's inputs" do
15
+ test_set, _ = @builder.send(:horizontal_growth, @test_pairs, @data[2], @data[0..1])
16
+
17
+ test_set.should == [[:A1, :B1, :C1],
18
+ [:A1, :B2, :C2],
19
+ [:A2, :B1, :C3],
20
+ [:A2, :B2, :C1]]
21
+ end
22
+
23
+ it "should return all the uncovered pairs" do
24
+ _, pi = @builder.send(:horizontal_growth, @test_pairs, @data[2], @data[0..1])
25
+
26
+ # We are getting the uncovered pairs in reverse
27
+ #pi.should == [[:A2, :C2],[:A1, :C3],[:B1, :C2],[:B2, :C3]]
28
+ # Cheat and check we get the list in reverse
29
+ pi.should == [[:C2, :A2], [:C2, :B1], [:C3, :A1], [:C3, :B2]]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Pairwise
4
+ describe Cli do
5
+
6
+ before(:each) do
7
+ Kernel.stub!(:exit).and_return(nil)
8
+ end
9
+
10
+ def output_stream
11
+ @output_stream ||= StringIO.new
12
+ end
13
+
14
+ def options
15
+ #TODO: push options out to an object and avoid hacking at private instance vars
16
+ @cli.instance_variable_get("@options")
17
+ end
18
+
19
+ def prepare_args(args)
20
+ args.is_a?(Array) ? args : args.split(' ')
21
+ end
22
+
23
+ def after_parsing(args)
24
+ @cli = Pairwise::Cli.new(prepare_args(args), output_stream)
25
+ @cli.parse!
26
+ yield
27
+ end
28
+
29
+ context '--keep-wild-cards' do
30
+ it "displays wild cards in output result" do
31
+ after_parsing('--keep-wild-cards') do
32
+ options[:keep_wild_cards].should == true
33
+ end
34
+ end
35
+ end
36
+
37
+ context "--help" do
38
+ it "displays usage" do
39
+ after_parsing('--help') do
40
+ output_stream.string.should =~ /Usage: pairwise \[options\] FILE\.yml/
41
+ end
42
+ end
43
+ end
44
+
45
+ context '--version' do
46
+ it "displays Pairwise's version" do
47
+ after_parsing('--version') do
48
+ output_stream.string.should =~ /#{Pairwise::VERSION}/
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,125 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Pairwise do
4
+ before(:each) do
5
+ Kernel.stub!(:rand).and_return(0)
6
+ end
7
+
8
+ context "with invalid inputs" do
9
+ it "should be invalid when running with no input" do
10
+ lambda{ Pairwise.combinations([]) }.should raise_error(Pairwise::InvalidInputData)
11
+ end
12
+
13
+ it "should be invalid when running with only 1 input" do
14
+ lambda{ Pairwise.combinations([:A1, :A2])}.should raise_error(Pairwise::InvalidInputData)
15
+ end
16
+
17
+ it "should be invalid when running with a single list input" do
18
+ lambda{ Pairwise.combinations([:A1, :A2])}.should raise_error(Pairwise::InvalidInputData)
19
+ end
20
+ end
21
+
22
+ context "with valid inputs" do
23
+ context "which are equal lengths" do
24
+ it "should generate pairs for 2 parameters of 1 value" do
25
+ data = [[:A1], [:B1]]
26
+
27
+ Pairwise.combinations(*data).should == [[:A1, :B1]]
28
+ end
29
+
30
+ it "should generate all pairs for 2 parameters of 2 values" do
31
+ data = [[:A1, :A2], [:B1, :B2]]
32
+
33
+ Pairwise.combinations(*data).should == [[:A1, :B1], [:A1, :B2], [:A2, :B1], [:A2, :B2]]
34
+ end
35
+
36
+ it "should generate all pairs for 3 parameters of 1 value" do
37
+ data = [[:A1], [:B1], [:C1]]
38
+
39
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1]]
40
+ end
41
+
42
+ it "should generate pairs for three paramters" do
43
+ data = [[:A1, :A2],
44
+ [:B1, :B2],
45
+ [:C1 , :C2]]
46
+
47
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
48
+ [:A1, :B2, :C2],
49
+ [:A2, :B1, :C2],
50
+ [:A2, :B2, :C1]]
51
+ end
52
+ end
53
+
54
+ context "which are unequal lenghts" do
55
+ it "should generate all pairs for 3 parameters of 1,1,2 values" do
56
+ data = [[:A1], [:B1], [:C1, :C2]]
57
+
58
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
59
+ [:A1, :B1, :C2]]
60
+ end
61
+
62
+ it "should generate all pairs for 3 parameters of 1,1,3 values" do
63
+ data = [[:A1], [:B1], [:C1, :C2, :C3]]
64
+
65
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
66
+ [:A1, :B1, :C2],
67
+ [:A1, :B1, :C3]]
68
+ end
69
+
70
+ it "should generate all pairs for 3 parameters of 1,2,3 values" do
71
+ data = [[:A1], [:B1, :B2], [:C1, :C2, :C3]]
72
+
73
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
74
+ [:A1, :B2, :C2],
75
+ [:A1, :B2, :C1],
76
+ [:A1, :B1, :C2],
77
+ [:A1, :B1, :C3],
78
+ [:A1, :B2, :C3]]
79
+ end
80
+
81
+ it "should generate all pairs for 3 parameters of 2,1,2 values" do
82
+ data = [[:A1, :A2], [:B1], [:C1, :C2]]
83
+
84
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
85
+ [:A2, :B1, :C2],
86
+ [:A2, :B1, :C1],
87
+ [:A1, :B1, :C2]]
88
+
89
+
90
+ #:A1, :B1, :C1
91
+ #:A1, :B1, :C2
92
+ #:A2, :B1, :C1
93
+ #:A2,any_value_of_B, :C2
94
+ end
95
+
96
+ it "should generate all pairs for 4 parameters of 2,1,2,2 values" do
97
+ data = [[:A1, :A2], [:B1], [:C1, :C2], [:D1, :D2]]
98
+
99
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1, :D1],
100
+ [:A2, :B1, :C2, :D2],
101
+ [:A2, :B1, :C1, :D2],
102
+ [:A1, :B1, :C2, :D2],
103
+ [:A2, :B1, :C2, :D1]]
104
+ #:A1, :B1, :C1, :D1
105
+ #:A1, :B1, :C2, :D2
106
+ #:A2, any_value_of_B, :C2, :D1
107
+ #:A2, :B1, :C1, :D2
108
+ end
109
+
110
+ it "should generate pairs for three paramters" do
111
+ data = [[:A1, :A2],
112
+ [:B1, :B2],
113
+ [:C1 , :C2 , :C3 ]]
114
+
115
+ Pairwise.combinations(*data).should == [[:A1, :B1, :C1],
116
+ [:A1, :B2, :C2],
117
+ [:A2, :B1, :C3],
118
+ [:A2, :B2, :C1],
119
+ [:A2, :B1, :C2],
120
+ [:A1, :B2, :C3]]
121
+ end
122
+ end
123
+ end
124
+
125
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format
3
+ profile
4
+ --diff
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/pairwise'
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pairwise
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joseph Wilk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-05 00:00:00 +00:00
13
+ default_executable: pairwise
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: A tool for selecting a smaller number of test combinations (using pairwise generation) rather than exhaustively testing all possible permutations.
36
+ email: joe@josephwilk.net
37
+ executables:
38
+ - pairwise
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ files:
44
+ - .gitignore
45
+ - LICENSE
46
+ - Rakefile
47
+ - VERSION.yml
48
+ - bin/pairwise
49
+ - features/generating_pairwise_data.feature
50
+ - features/step_definitions/pairwise_data_steps.rb
51
+ - features/support/env.rb
52
+ - features/support/hooks.rb
53
+ - gem_tasks/features.rake
54
+ - gem_tasks/rspec.rake
55
+ - lib/pairwise.rb
56
+ - lib/pairwise/builder.rb
57
+ - lib/pairwise/cli.rb
58
+ - lib/pairwise/formatter.rb
59
+ - lib/pairwise/formatter/cucumber.rb
60
+ - lib/pairwise/test_pair.rb
61
+ - spec/pairwise/builder_spec.rb
62
+ - spec/pairwise/cli_spec.rb
63
+ - spec/pairwise_spec.rb
64
+ - spec/spec.opts
65
+ - spec/spec_helper.rb
66
+ has_rdoc: true
67
+ homepage: http://wiki.github.com/josephwilk/pairwise
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.3.5
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Turn large test data combinations into smaller ones
94
+ test_files:
95
+ - spec/pairwise/builder_spec.rb
96
+ - spec/pairwise/cli_spec.rb
97
+ - spec/pairwise_spec.rb
98
+ - spec/spec_helper.rb