pairwise 0.1.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.
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