Skip to content

Instantly share code, notes, and snippets.

@eightbitraptor
Created November 14, 2011 21:02
Show Gist options
  • Save eightbitraptor/1365150 to your computer and use it in GitHub Desktop.
Save eightbitraptor/1365150 to your computer and use it in GitHub Desktop.
A first pass solution for @martinrue's Ordered Jobs Kata
# started writing the tests for OrderedJobs class
# wrote no jobs and a single job, when I got to single job
# left that test failing because I realised I needed some
# kind of Job, and potentially a parser.
#
# Started working on a job class, tdd'd it to have a name
# and an optional dependancy, and nothing else.
#
# Still need a way of getting strings into Jobs to make my
# single job test case pass though, to keep the OrderedJobs
# class just ordering jobs I opt to create a JobsParser and
# TDD that. The JobsParser can then pass a list of jobs back
# to the OrderedJobs class which will sort them and format
# the output
#
# For the sorting of the output, TDD'd it because I'm not smart
# enough to design a big sorting system up front.
#
# Also using Ruby stdlib Set for an easy way of determining when
# al element is in a collection twice.
#
# In retrospect I think I'd have liked a JobList class or something
# like that to handle getting and setting.
#
# …Other thoughts, in reflection, are that I've probably dissapeared
# into Architectural astronaut land. If @cwninja where here he'd have
# taken my keyboard away by now.
require 'set'
class OrderedJobs
def self.process(jobs_spec)
@joblist = JobParser.parse(jobs_spec)
sort!(JobParser.parse(jobs_spec))
end
def self.cyclic_dependancy_in(jobs)
jobs.each do |job_name, job|
dep_chain = Set.new
while(job = jobs[job.dependancy])
break if !job.dependancy
if dep_chain.add?(job.name).nil?
return true
end
end
end
return false
end
def self.sort!(jobs)
raise(StandardError) if cyclic_dependancy_in(jobs)
jobs.values.inject([]) { |sorted_jobs, job|
if !sorted_jobs.include? job.name
sorted_jobs << job.name
end
if job.dependancy
sorted_jobs.delete(job.dependancy)
job_pos = sorted_jobs.index(job.name)
sorted_jobs.insert(job_pos, job.dependancy)
end
sorted_jobs
}
end
end
class Job
attr_accessor :name, :dependancy
def initialize(name, dep=nil)
if dep == name
raise ArgumentError.new("Jobs cannot depend on themselves")
end
@name = name
@dependancy = (dep == "") ? nil : dep
end
def dependancy?
!!@dependancy
end
end
class JobParser
def self.parse(job_spec)
job_spec.split("\n").inject(Hash.new){ |acc, spec_line|
j = Job.new(*spec_line.split('=>').map(&:strip))
acc[j.name] = j
acc
}
end
end
require 'rspec'
describe JobParser do
let(:parsed_job) { JobParser.parse("a => b") }
context "when given a single job" do
it "indexes the collection by the jobname" do
parsed_job.keys.should include("a")
parsed_job.keys.length.should be(1)
end
it "stores jobs in the collection" do
parsed_job["a"].should be_a(Job)
end
it "names the job correctly" do
parsed_job["a"].name.should eq("a")
end
it "sets up the correct dependancy" do
parsed_job["a"].dependancy.should eq("b")
end
end
context "with multiple jobs" do
let(:parsed_jobs){ JobParser.parse("a => b
b =>
c => ")}
it "creates the correct number of jobs" do
parsed_jobs.length.should be(3)
end
it "parses the args correctly" do
parsed_jobs.values.map(&:name).should =~ ["a", "b", "c"]
end
end
end
describe Job do
it "is invalid without a name" do
expect{
Job.new
}.to raise_error(ArgumentError)
end
it "cannot depend on itself" do
expect{
Job.new("a", "a")
}.to raise_error(ArgumentError)
end
it "returns it's name when asked" do
Job.new(:test).name.should be :test
end
context "without a dependancy" do
it "returns nil dependancy" do
Job.new(:test).dependancy.should be_nil
end
end
context "when a dependancy is supplied" do
it "returns the dependancy when asked" do
Job.new(:test, :dep).dependancy.should eq(:dep)
end
end
end
describe OrderedJobs do
context "When I pass no jobs" do
it "returns an empty collection" do
OrderedJobs.process("").should eq([])
end
end
context "when I pass a single job" do
it "returns the job to be run" do
OrderedJobs.process("a => ").should eq(["a"])
end
end
context "multiple jobs, single dependancy" do
let(:job_spec){ "a =>
b => c
c =>" }
it "puts c before b" do
jobs = OrderedJobs.process(job_spec)
jobs.join.should =~ /c.*b/
end
end
context "multiple jobs, multiple dependancies" do
let(:jobs){ OrderedJobs.process("a =>
b => c
c => f
d => a
e => b
f => ")}
it "puts f before c" do
jobs.join.should =~ /f.*c/
end
it "puts c before b" do
jobs.join.should =~ /c.*b/
end
it "puts b before e" do
jobs.join.should =~ /b.*e/
end
it "puts a before d" do
jobs.join.should =~ /a.*d/
end
it "contains all six letters" do
jobs.length.should be(6)
end
end
context "self referential jobs" do
let(:job_spec){ "a =>
b => c
c => f
d => a
e =>
f => b"}
it "raises an Error" do
expect {
OrderedJobs.process(job_spec)
}.to raise_error(StandardError)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment