marmalade 0.0.2 → 0.0.3

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.
@@ -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