marmalade 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ # File Fix-It Example
2
+
3
+ A working example of the 'File Fix-it' puzzle at https://code.google.com/codejam/contest/635101/dashboard#s=p0
4
+
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib'))
4
+ require 'marmalade'
5
+
6
+ class TestCase
7
+ def each_dir(path)
8
+ path.split('/').reject { |d| d.nil? || d == '' }.each do |dir|
9
+ yield dir
10
+ end
11
+ end
12
+
13
+ def mkdir(path, root)
14
+ created = 0
15
+ cwd = root
16
+ puts_dbg "path: #{path}"
17
+ each_dir(path) do |dir|
18
+ puts_dbg "dir: #{dir}"
19
+ unless cwd.include?(dir)
20
+ puts_dbg "needs creating"
21
+ cwd[dir] = {}
22
+ created += 1
23
+ end
24
+ cwd = cwd[dir]
25
+ end
26
+ created
27
+ end
28
+
29
+ def solve(existing, to_create)
30
+ root = {}
31
+ existing.each do |path|
32
+ cwd = root
33
+ each_dir(path) do |dir|
34
+ cwd[dir] ||= {}
35
+ cwd = cwd[dir]
36
+ end
37
+ end
38
+ created = 0
39
+ to_create.each do |path|
40
+ created += mkdir(path, root)
41
+ end
42
+ created
43
+ end
44
+ end
45
+
46
+ Marmalade.jam do
47
+ read_num_cases
48
+ test_cases do
49
+ read [:e, :c], :type => :int
50
+ read :existing, :count => @e
51
+ read :to_create, :count => @c
52
+ run_case do
53
+ puts_dbg "Existing:"
54
+ @existing.each { |f| puts_dbg f }
55
+ puts_dbg "To create:"
56
+ @to_create.each { |f| puts_dbg f }
57
+ puts solve(@existing, @to_create)
58
+ end
59
+ end
60
+ end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib'))
4
4
  require 'marmalade'
5
5
 
6
6
  Marmalade.jam do
@@ -8,6 +8,7 @@ Marmalade.jam do
8
8
  test_cases do
9
9
  read :words, :split => true
10
10
  run_case do
11
+ puts_dbg "Input was: #{@words.inspect}"
11
12
  puts @words.reverse.join(' ')
12
13
  end
13
14
  end
@@ -1,25 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib'))
4
4
  require 'marmalade'
5
5
 
6
- def intersect?(segment_a, segment_b)
7
- # They intersect if only one side is higher than the other
8
- (segment_a[0] - segment_b[0]) * (segment_a[1] - segment_b[1]) < 0
9
- end
6
+ class TestCase
7
+ def intersect?(segment_a, segment_b)
8
+ # They intersect if only one side is higher than the other
9
+ (segment_a[0] - segment_b[0]) * (segment_a[1] - segment_b[1]) < 0
10
+ end
10
11
 
11
- # Brute force check all wires
12
- def count_intersections(wires)
13
- intersections = 0
14
- wires.length.times do
15
- wire = wires.pop
16
- puts_dbg "Wire: #{wire.inspect}"
17
- wires.each do |other_wire|
18
- puts_dbg "Other wire: #{other_wire.inspect}"
19
- intersections += intersect?(wire, other_wire) ? 1 : 0
12
+ # Brute force check all wires
13
+ def count_intersections(wires)
14
+ intersections = 0
15
+ wires.length.times do
16
+ wire = wires.pop
17
+ puts_dbg "Wire: #{wire.inspect}"
18
+ wires.each do |other_wire|
19
+ puts_dbg "Other wire: #{other_wire.inspect}"
20
+ intersections += intersect?(wire, other_wire) ? 1 : 0
21
+ end
20
22
  end
23
+ intersections
21
24
  end
22
- intersections
23
25
  end
24
26
 
25
27
  Marmalade.jam do
@@ -1,70 +1,96 @@
1
- # TODO: RDoc this
2
- # TODO: read_array
3
- # TODO: read_integer_array
4
- # TODO: read_values
1
+ require 'parallel'
5
2
 
6
3
  module Marmalade
7
4
  class Puzzle
8
5
  attr_accessor :reader
9
6
 
10
7
  def initialize(file_reader, options = {})
11
- @options = options.dup
12
- @debug = (@options.delete(:debug) == true)
13
8
  self.reader = file_reader
9
+ @options = options.dup
10
+ @debug = (@options[:debug] == true)
11
+ if @options[:processes].to_i > 1 && !@options[:parallel]
12
+ @options[:parallel] = true
13
+ end
14
+ end
15
+
16
+ def read_num_cases
17
+ read :num_cases, :type => :int
14
18
  end
15
19
 
16
20
  def read(assignments, options = {})
17
- if @running_case
18
- raise MarmaladeError.new("Cannot call read while in a run_case block")
19
- end
20
- options = @options.merge(options)
21
21
  assigns = reader.read(assignments, options)
22
22
  assigns.each do |k, v|
23
+ # Set an ivar on both the puzzle and the test case so we can support using them in
24
+ # subsequent calls to 'read' in the same block evaluation.
23
25
  instance_variable_set("@#{k.to_s}", v)
26
+ if @current_case
27
+ @current_case.instance_variable_set("@#{k.to_s}", v)
28
+ end
24
29
  end
25
30
  end
26
31
 
27
- def read_num_cases
28
- read :num_cases, :type => :int
29
- end
30
-
31
32
  def test_cases(options = {}, &block)
32
33
  unless @num_cases.is_a?(Fixnum)
33
34
  raise MarmaladeError.new("@num_cases has not been set or is not an integer")
34
35
  end
35
36
  options = options.merge(@options)
36
- 1.upto(@num_cases) do |case_num|
37
- @case_num = case_num
38
- instance_eval(&block)
39
- return if options[:case] == case_num
40
- STDIN.getc if options[:step]
41
- end
42
- end
43
37
 
44
- def run_case(&block)
45
- @running_case = true
46
- if @options[:case].nil? || @options[:case] == @case_num
38
+ # Set up all test cases and read each one's info from the file
39
+ test_cases = []
40
+ 1.upto(@num_cases).each do |case_num|
41
+ test_case = TestCase.new(case_num, options)
42
+ test_case.debug = @debug
43
+
44
+ @current_case = test_case
47
45
  instance_eval(&block)
46
+ @current_case = nil
47
+
48
+ test_cases << test_case
48
49
  end
49
- @running_case = false
50
- end
51
50
 
52
- def puts(*args)
53
- puts_with_case(*args)
51
+ # If we are to run in more than one process, we'll run all of the tests without
52
+ # regard to stepping or finding a single case to run
53
+ if options[:parallel]
54
+ # Setup pipes for the output of each test case
55
+ reader, writer = IO.pipe
56
+ test_cases.each do |test_case|
57
+ test_case.output = writer
58
+ end
59
+ # Run them
60
+ processes = options[:processes] || Parallel.processor_count
61
+ Parallel.each(test_cases, :in_processes => processes) do |test_case|
62
+ test_case.run
63
+ end
64
+ # Now output the buffers from each case
65
+ writer.close
66
+ outputs = []
67
+ # TODO: This could probably be done with a stable sort instead...
68
+ while message = reader.gets
69
+ match = message.match(/Case #(\d+)/)
70
+ if match
71
+ case_num = match[1].to_i
72
+ case_output = outputs[case_num] ||= []
73
+ case_output << message
74
+ else
75
+ outputs << message
76
+ end
77
+ end
78
+ outputs.flatten.compact.each { |msg| puts msg }
79
+ else
80
+ test_cases.each do |test_case|
81
+ if options[:case].nil? || options[:case] == test_case.case_num
82
+ test_case.run
83
+ if options[:step] == true
84
+ STDIN.getc if options[:step]
85
+ end
86
+ end
87
+ end
88
+ end
54
89
  end
55
90
 
56
- def puts_dbg(*args)
57
- puts_with_case(*args) if @debug
91
+ def run_case(&block)
92
+ @current_case.run_block = block
58
93
  end
59
94
 
60
- private
61
-
62
- def puts_with_case(*args)
63
- unless @case_num.nil?
64
- print "Case ##{@case_num}: "
65
- end
66
- print *args
67
- print "\n"
68
- end
69
95
  end
70
96
  end
@@ -0,0 +1,37 @@
1
+ class TestCase
2
+ attr_accessor :case_num
3
+ attr_accessor :debug
4
+ attr_accessor :output
5
+ attr_accessor :run_block
6
+
7
+ def initialize(case_num, options = {})
8
+ self.case_num = case_num
9
+ @output = options[:output] || STDOUT
10
+ @options = options
11
+ end
12
+
13
+ def run
14
+ unless @run_block.nil?
15
+ instance_eval(&@run_block)
16
+ end
17
+ end
18
+
19
+ def puts(*args)
20
+ puts_with_case(*args)
21
+ end
22
+
23
+ def puts_dbg(*args)
24
+ puts_with_case(*args) if debug
25
+ end
26
+
27
+ private
28
+
29
+ def puts_with_case(*args)
30
+ message = args.map(&:to_s).join("\n") + "\n"
31
+ unless @case_num.nil?
32
+ message = "Case ##{@case_num}: #{message}"
33
+ end
34
+ @output.print message
35
+ end
36
+
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Marmalade
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/marmalade.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'trollop'
3
+ require 'parallel'
3
4
 
4
5
  require 'marmalade/project_builder'
5
6
  require 'marmalade/file_reader'
7
+ require 'marmalade/test_case'
6
8
  require 'marmalade/puzzle'
7
9
  require 'marmalade/version'
8
10
 
@@ -18,7 +20,7 @@ module Marmalade
18
20
  unless File.exist?(file_name)
19
21
  raise MarmaladeError.new("Cannot find input file #{file_name}")
20
22
  end
21
- File.open(options[:file], 'r') do |file|
23
+ File.open(file_name, 'r') do |file|
22
24
  reader = FileReader.new(file)
23
25
  puzzle = Puzzle.new(reader, options)
24
26
  puzzle.instance_eval(&block)
@@ -31,11 +33,13 @@ module Marmalade
31
33
  private
32
34
 
33
35
  def self.parse_options
34
- options = Trollop::options do
36
+ Trollop::options do
35
37
  opt :file, "Input file to read", :short => 'f', :type => :string
36
38
  opt :debug, "Debug mode", :short => 'd', :default => false
37
39
  opt :step, "Step through each case", :short => 's', :default => false
38
40
  opt :case, "Only run the given case", :short => 'c', :type => :integer, :default => nil
41
+ opt :parallel, "Run the cases in parallel processes", :short => 'p', :default => false
42
+ opt :processes, "Specify the number of processes to use", :type => :integer
39
43
  end
40
44
  end
41
45
 
data/marmalade.gemspec CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Marmalade::VERSION
17
17
 
18
18
  gem.add_dependency 'trollop', '~>1.16.2'
19
+ gem.add_dependency 'parallel', '0.6.2'
19
20
 
20
21
  gem.add_development_dependency 'rspec', '~>2.9.0'
21
22
  gem.add_development_dependency 'mocha', '~>0.10.5'
@@ -2,13 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  describe Marmalade::Puzzle do
4
4
 
5
- describe "#read" do
5
+ describe '#read' do
6
6
  before do
7
7
  @reader = mock()
8
8
  @puzzle = Marmalade::Puzzle.new(@reader)
9
9
  end
10
-
11
- it "will pass the options to the reader and assign instance varialbes based on the results" do
10
+ it "assigns instance variables based on the results from the file reader" do
12
11
  @reader.expects(:read).with([:foo, :bar], { :a => 1 }).returns({:foo => "a", :bar => 2})
13
12
  @puzzle.read([:foo, :bar], :a => 1)
14
13
  @puzzle.instance_eval do
@@ -16,129 +15,132 @@ describe Marmalade::Puzzle do
16
15
  @bar.should == 2
17
16
  end
18
17
  end
18
+ it 'assigns the instance variables to the current test case if there is one' do
19
+ @reader.expects(:read).with([:foo, :bar], { :a => 1 }).returns({:foo => "a", :bar => 2})
20
+ current_case = Object.new
21
+ @puzzle.instance_variable_set('@current_case', current_case)
22
+ @puzzle.read([:foo, :bar], :a => 1)
23
+ current_case.instance_eval do
24
+ @foo.should == 'a'
25
+ @bar.should == 2
26
+ end
27
+ end
28
+ end
19
29
 
20
- it "will read in the number of cases with read_num_cases" do
30
+ describe '#read_num_cases' do
31
+ before do
32
+ @reader = mock()
33
+ @puzzle = Marmalade::Puzzle.new(@reader)
34
+ end
35
+ it "will use the reader to read in the number of cases" do
21
36
  @reader.expects(:read).with(:num_cases, :type => :int).returns({:num_cases => 1234})
22
37
  @puzzle.read_num_cases
23
38
  @puzzle.instance_eval do
24
39
  @num_cases.should == 1234
25
40
  end
26
41
  end
27
-
28
- it "will raise an error if it is called in a run_case block" do
29
- expect do
30
- @puzzle.run_case do
31
- read :foo
32
- end
33
- end.to raise_error(MarmaladeError, /Cannot call read while in a run_case block/)
34
- end
35
-
36
42
  end
37
43
 
38
- describe "#test_cases" do
44
+ describe "test_cases and run_case blocks" do
39
45
  before do
40
- @puzzle = Marmalade::Puzzle.new(mock('reader'))
41
- end
42
-
43
- it "will run through all the test cases" do
44
- run_test_cases.should == [1, 2, 3]
45
- end
46
-
47
- it "will return after the specified case as part of support for run_case" do
48
- run_test_cases(:case => 2).should == [1, 2]
49
- end
50
-
51
- it "will pause after each case if the :step option is set" do
52
- STDIN.expects(:getc).times(3)
53
- run_test_cases(:step => true)
54
- end
55
-
56
- it "will raise an error if num_cases hasn't been set" do
57
- expect { @puzzle.test_cases }.to raise_error(MarmaladeError, /has not been set/)
58
- end
59
-
60
- def run_test_cases(options = {})
46
+ @reader = mock('reader')
47
+ @puzzle = Marmalade::Puzzle.new(@reader)
61
48
  @puzzle.instance_eval do
62
49
  @num_cases = 3
63
50
  end
64
- case_numbers = []
65
- @puzzle.test_cases(options) do
66
- case_numbers << @case_num
67
- end
68
- case_numbers
69
- end
70
- end
71
-
72
- describe "#run_case" do
73
- before do
74
- @puzzle = Marmalade::Puzzle.new(mock('file_reader'))
75
51
  end
76
52
 
77
- it "will evaluate the given block if no case options are specified" do
78
- value = nil
79
- build_puzzle(3).run_case do
80
- value = 'foo'
53
+ it 'sets the options for each TestCase base on its own options' do
54
+ values = []
55
+ @puzzle.test_cases :opt1 => :foo, :opt2 => 'bar' do
56
+ run_case do
57
+ values << @options
58
+ end
59
+ end
60
+ values.each do |options|
61
+ options.should == {:opt1 => :foo, :opt2 => 'bar'}
81
62
  end
82
- value.should == 'foo'
83
63
  end
84
64
 
85
- it "will set @running_case to true while in the block" do
86
- running = false
87
- build_puzzle(3).run_case do
88
- running = @running_case
65
+ it 'sets the case_num for each TestCase' do
66
+ values = []
67
+ @puzzle.test_cases do
68
+ run_case do
69
+ values << case_num
70
+ end
89
71
  end
90
- running.should be_true
72
+ values.should == [1, 2, 3]
91
73
  end
92
74
 
93
- it "will evaluate the block if the given case and the current case match" do
94
- value = nil
95
- build_puzzle(3, :case => 3).run_case do
96
- value = 'foo'
75
+ it 'sets the debug flag for each test case if debug is set' do
76
+ @puzzle.instance_eval do
77
+ @debug = true
97
78
  end
98
- value.should == 'foo'
79
+ values = []
80
+ @puzzle.test_cases do
81
+ run_case do
82
+ values << debug
83
+ end
84
+ end
85
+ values.should == [true, true, true]
99
86
  end
100
87
 
101
- it "will not evalulate the block if the case numbers don't match" do
102
- value = nil
103
- build_puzzle(3, :case => 4).run_case do
104
- value = 'foo'
88
+ it 'sets test case instance variables based on what is read from the file' do
89
+ @reader.expects(:read).with(:foo, {}).times(3).
90
+ returns({:foo => :a}, {:foo => :b}, {:foo => :c})
91
+ values = []
92
+ @puzzle.test_cases do
93
+ read :foo
94
+ run_case do
95
+ values << @foo
96
+ end
105
97
  end
106
- value.should be_nil
98
+ values.should == [:a, :b, :c]
107
99
  end
108
- end
109
100
 
110
- describe "#puts" do
111
- it "will print the message along with the current case number" do
112
- puzzle = build_puzzle(4)
113
- puzzle.expects(:print).with("Case #4: ")
114
- puzzle.expects(:print).with("hello")
115
- puzzle.expects(:print).with("\n")
116
- puzzle.puts("hello")
101
+ it 'only runs the given case if the :case option is set' do
102
+ values = []
103
+ @puzzle.test_cases :case => 2 do
104
+ run_case do
105
+ values << case_num
106
+ end
107
+ end
108
+ values.should == [2]
117
109
  end
118
- end
119
110
 
120
- describe "#puts_dbg" do
121
- it "will print the message with the case number if in debug mode" do
122
- puzzle = build_puzzle(4, :debug => true)
123
- puzzle.expects(:print).with("Case #4: ")
124
- puzzle.expects(:print).with("hello")
125
- puzzle.expects(:print).with("\n")
126
- puzzle.puts_dbg('hello')
111
+ it "pauses after each case if the :step option is set" do
112
+ STDIN.expects(:getc).times(3)
113
+ @puzzle.test_cases :step => true do
114
+ run_case do
115
+ end
116
+ end
127
117
  end
128
118
 
129
- it "will not print the message if the puzzle isn't in debug mode" do
130
- puzzle = build_puzzle(4)
131
- puzzle.expects(:print).never
132
- puzzle.puts_dbg('hello')
119
+ it "raises an error if num_cases hasn't been set" do
120
+ @puzzle.instance_eval { @num_cases = nil }
121
+ expect do
122
+ @puzzle.test_cases do
123
+ puts "this won't get called."
124
+ end
125
+ end.to raise_error(MarmaladeError, /has not been set/)
126
+ end
127
+
128
+ context 'when the parallel option is set' do
129
+ it 'runs the cases in parallel and prints the output in order' do
130
+ sleep_times = [0.75, 0.25, 0.5]
131
+ test_output = sequence('test_output')
132
+ @puzzle.expects(:puts).with("Case #1: hello\n").in_sequence(test_output)
133
+ @puzzle.expects(:puts).with("Case #2: hello\n").in_sequence(test_output)
134
+ @puzzle.expects(:puts).with("Case #3: hello\n").in_sequence(test_output)
135
+ @puzzle.test_cases :parallel => true do
136
+ run_case do
137
+ sleep sleep_times[case_num - 1]
138
+ puts "hello"
139
+ end
140
+ end
141
+ end
133
142
  end
134
- end
135
143
 
136
- def build_puzzle(case_num, options = {})
137
- puzzle = Marmalade::Puzzle.new(mock('file_reader'), options)
138
- puzzle.instance_eval do
139
- @case_num = case_num
140
- end
141
- puzzle
142
144
  end
143
145
 
144
146
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe TestCase do
4
+
5
+ before do
6
+ @test_case = TestCase.new(4)
7
+ end
8
+
9
+ describe '#initialize' do
10
+ it 'sets the case_num' do
11
+ @test_case.case_num.should == 4
12
+ end
13
+ end
14
+
15
+ describe "#puts" do
16
+ it 'will print the message along with the current case number to STDOUT' do
17
+ STDOUT.expects(:print).with("Case #4: hello\n")
18
+ @test_case.puts("hello")
19
+ end
20
+ it 'will print to the supplied IO buffer if an output option is given' do
21
+ output = mock()
22
+ output.expects(:print).with("Case #4: hello\n")
23
+ @test_case.output = output
24
+ @test_case.puts("hello")
25
+ end
26
+ end
27
+
28
+ describe "#puts_dbg" do
29
+ it "will print the message with the case number if in debug mode" do
30
+ @test_case.debug = true
31
+ STDOUT.expects(:print).with("Case #4: hello\n")
32
+ @test_case.puts_dbg('hello')
33
+ end
34
+
35
+ it "will not print the message if the @test_case isn't in debug mode" do
36
+ @test_case.debug = false
37
+ STDOUT.expects(:print).never
38
+ @test_case.puts_dbg('hello')
39
+ end
40
+ end
41
+
42
+ end
data/templates/main.rb CHANGED
@@ -3,12 +3,20 @@
3
3
  require 'rubygems'
4
4
  require 'marmalade'
5
5
 
6
+ class TestCase
7
+ # Any methods you want to call in your individual cases can be defined here.
8
+ end
9
+
6
10
  Marmalade.jam do
11
+
7
12
  # This assumes your input file has the number of test cases as the first line.
8
- # Remove the call to read_num_cases this if that is not so.
9
13
  read_num_cases
10
14
 
11
15
  test_cases do
12
- # Test case-specific code goes here
16
+ # read information about the test case here.
17
+ run_case do
18
+ # Test case-specific code goes here.
19
+ end
13
20
  end
21
+
14
22
  end