local_ci 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6da45961cab3661dd38b4c4c8df3317eb9ddd6b995f6dfa95e5d4083f704c3d1
4
- data.tar.gz: a9545621f1d8baaf69730bc631a6f73f8c261e1740613f763348aae2233c2c8e
3
+ metadata.gz: 83a9843902d865aac3c8a3d995129379db5661f71fbece0537137f45fbed0b7e
4
+ data.tar.gz: 535d98f227e41f2aba8bbab39ec28b304d89cff2599cc2dc646cb103dc7ee465
5
5
  SHA512:
6
- metadata.gz: fb9e176666f22257c6ac55dfa8991ed056acd330656312b61d9569c6bd764f0c9caf28064dbfb5252b4e30751c7026c22f5b20577da38b3de5995a3b760fcd1f
7
- data.tar.gz: cdced51659d21eb82e050297a9267e8441e563eed6d2277f763c5bc1e5f3c2b530c2ee69cb6f26e24bbc75d2bf45e8a98c9467f475f703ccdc6c3d56516ebc5f
6
+ metadata.gz: 60376c8a194767876646b90604047e387071f46e77742e6d26ccc5f53ae69e39aa61633511482b4b6a27f2d9ec98a6369862740fcff862e8e030b0a6c16a4f31
7
+ data.tar.gz: 261ed6fce994c12fc161fa8c8c86bd580fa5c4ae228364e83d786f2b5af2a9e53b75db96b2636956e078417d9b430dbfdbcc3e58f2551e452dbe69aa7a894519
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.log
2
+ *.gem
data/Gemfile CHANGED
@@ -6,5 +6,5 @@ group :development do
6
6
  end
7
7
 
8
8
  group :test do
9
- gem "neospec"
9
+ gem "rspec"
10
10
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- local_ci (0.0.1)
4
+ local_ci (0.0.2)
5
5
  logger
6
6
  pastel
7
7
  rake
@@ -12,11 +12,11 @@ GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
14
  ast (2.4.3)
15
- json (2.16.0)
15
+ diff-lcs (1.6.2)
16
+ json (2.17.1)
16
17
  language_server-protocol (3.17.0.5)
17
18
  lint_roller (1.1.0)
18
19
  logger (1.7.0)
19
- neospec (0.0.1)
20
20
  parallel (1.27.0)
21
21
  parser (3.3.10.0)
22
22
  ast (~> 2.4.1)
@@ -28,6 +28,19 @@ GEM
28
28
  rainbow (3.1.1)
29
29
  rake (13.3.1)
30
30
  regexp_parser (2.11.3)
31
+ rspec (3.13.2)
32
+ rspec-core (~> 3.13.0)
33
+ rspec-expectations (~> 3.13.0)
34
+ rspec-mocks (~> 3.13.0)
35
+ rspec-core (3.13.6)
36
+ rspec-support (~> 3.13.0)
37
+ rspec-expectations (3.13.5)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.13.0)
40
+ rspec-mocks (3.13.7)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.13.0)
43
+ rspec-support (3.13.6)
31
44
  rubocop (1.81.7)
32
45
  json (~> 2.3)
33
46
  language_server-protocol (~> 3.17.0.2)
@@ -77,7 +90,7 @@ PLATFORMS
77
90
 
78
91
  DEPENDENCIES
79
92
  local_ci!
80
- neospec
93
+ rspec
81
94
  standardrb
82
95
 
83
96
  BUNDLED WITH
data/Rakefile CHANGED
@@ -1,35 +1,44 @@
1
+ require "standard/rake"
1
2
  require "local_ci"
2
3
 
3
- CHUNK = 15
4
+ LocalCI::Rake.setup(self)
4
5
 
5
- desc "Run the CI suite locally"
6
- task :local_ci
6
+ setup do
7
+ job "Bundle", "bundle check | bundle install"
8
+ end
7
9
 
8
- LocalCI.flow(:linting, "Linting") do
9
- job "StandardRB" do
10
- run "bundle exec standardrb"
11
- end
10
+ teardown do
11
+ job "Echo", "echo", "global teardown"
12
12
  end
13
13
 
14
- LocalCI.flow(:build, "Build") do
15
- CHUNK.times do |i|
16
- job "build-#{i}" do
17
- run "sleep 1"
18
- end
14
+ flow "Linting" do
15
+ job "StandardRB", "bundle exec rake standard"
16
+ end
17
+
18
+ flow "Specs" do
19
+ setup do
20
+ job "Specs Setup", "echo specs setup"
19
21
  end
22
+
23
+ teardown do
24
+ job "Specs Teardown", "echo specs teardown"
25
+ end
26
+
27
+ job "RSpec", "bundle exec rspec"
20
28
  end
21
29
 
22
- LocalCI.flow(:lint, "Lint") do
23
- CHUNK.times do |i|
24
- job "lint-#{i}" do
25
- run "sleep 1"
26
- run "echo 'hi'"
30
+ flow "Build" do
31
+ setup do
32
+ job "Start docker", "echo docker start"
33
+ end
34
+
35
+ Dir.glob("*").each do |file|
36
+ job "Compile - #{file}" do
37
+ run "echo", "gcc", file
27
38
  end
28
39
  end
29
- end
30
40
 
31
- namespace :standardrb do
32
- task :fix do
33
- `bundle exec standardrb --fix-unsafely`
41
+ teardown do
42
+ job "Stop Docker", "echo docker stop"
34
43
  end
35
44
  end
@@ -0,0 +1,30 @@
1
+ module LocalCI
2
+ module DSL
3
+ def setup(heading = "Setup", parallel: false, &block)
4
+ LocalCI::Flow.new(
5
+ name: :setup,
6
+ heading: heading,
7
+ parallel: parallel,
8
+ actions: false,
9
+ block: block
10
+ )
11
+ end
12
+
13
+ def teardown(heading = "Teardown", parallel: false, &block)
14
+ LocalCI::Flow.new(
15
+ name: :teardown,
16
+ heading: heading,
17
+ parallel: parallel,
18
+ actions: false,
19
+ block: block
20
+ )
21
+ end
22
+
23
+ def flow(name, heading = nil, parallel: true, &block)
24
+ heading ||= name
25
+ name = LocalCI::Helper.taskize(heading) unless name.is_a?(Symbol)
26
+
27
+ LocalCI::Flow.new(name: name, heading: heading, parallel: parallel, block: block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ module LocalCI
2
+ class ExecContext
3
+ attr_reader :flow
4
+
5
+ def initialize(flow:)
6
+ @flow = flow
7
+ end
8
+
9
+ def run(command, *args)
10
+ LocalCI::Helper.runner.run command, *args
11
+ end
12
+
13
+ def job(name, *args, &block)
14
+ LocalCI::Job.new(
15
+ flow: @flow,
16
+ name: name,
17
+ command: args,
18
+ block: block
19
+ )
20
+ end
21
+
22
+ def setup(heading = nil, parallel: false, &block)
23
+ heading ||= "#{@flow.heading} - Setup"
24
+ LocalCI::Flow.new(
25
+ name: "#{@flow.task}:setup",
26
+ heading: heading,
27
+ parallel: parallel,
28
+ actions: false,
29
+ block: block
30
+ )
31
+ end
32
+
33
+ def teardown(heading = nil, parallel: false, &block)
34
+ heading ||= "#{@flow.heading} - Teardown"
35
+ LocalCI::Flow.new(
36
+ name: "#{@flow.task}:teardown",
37
+ heading: heading,
38
+ parallel: parallel,
39
+ actions: false,
40
+ block: block
41
+ )
42
+ end
43
+ end
44
+ end
@@ -1,16 +1,18 @@
1
1
  module LocalCI
2
2
  class Failure
3
- def pastel = @pastel ||= Pastel.new
3
+ attr_reader :job
4
4
 
5
5
  def initialize(job:, message:)
6
6
  @job = job
7
7
  @message = message
8
8
  end
9
9
 
10
- def display
11
- puts "#{pastel.bold.red("FAIL:")} #{@job}"
12
- puts @message
13
- puts
10
+ def message
11
+ <<~STR
12
+ #{LocalCI::Helper.pastel.bold.red("FAIL:")} #{@job}
13
+ #{@message}
14
+
15
+ STR
14
16
  end
15
17
  end
16
18
  end
data/lib/local_ci/flow.rb CHANGED
@@ -1,19 +1,19 @@
1
1
  module LocalCI
2
2
  class Flow
3
- attr_reader :heading, :job_spinner
3
+ attr_accessor :task, :heading, :spinner, :failures
4
4
 
5
- def pastel = @pastel ||= Pastel.new
6
- def runner = @runner ||= TTY::Command.new(output: Logger.new("ci.log"))
7
-
8
- def initialize(task:, heading:, parallel:, block:)
5
+ def initialize(name:, heading:, parallel:, block:, actions: true)
6
+ @task = name
7
+ @task = "ci:#{name}" unless @task.start_with?("ci:")
8
+ @parallel = parallel
9
9
  @failures = []
10
- @task = task
11
10
  @heading = heading
12
- @job_spinner = TTY::Spinner::Multi.new(
13
- "[:spinner] #{pastel.bold.blue(heading)}",
11
+ @actions = actions
12
+ @spinner = TTY::Spinner::Multi.new(
13
+ "[:spinner] #{LocalCI::Helper.pastel.bold.blue(@heading)}",
14
14
  format: :classic,
15
- success_mark: pastel.green("✓"),
16
- error_mark: pastel.red("✗"),
15
+ success_mark: LocalCI::Helper.pastel.green("✓"),
16
+ error_mark: LocalCI::Helper.pastel.red("✗"),
17
17
  style: {
18
18
  top: "",
19
19
  middle: " ",
@@ -21,44 +21,80 @@ module LocalCI
21
21
  }
22
22
  )
23
23
 
24
- klass = parallel ? Rake::MultiTask : Rake::Task
25
- @flow = klass.define_task(task) do
26
- @failures.each do |failure|
27
- failure.display
28
- end
29
-
30
- abort pastel.red("#{@heading} failed, see CI.log for more.") if @failures.any?
24
+ setup_required_tasks
25
+ if actions?
26
+ setup_flow_tasks
27
+ else
28
+ setup_actionless_flow_tasks
31
29
  end
32
30
 
33
- @flow.comment = heading
34
- Rake::Task[:local_ci].prerequisites << @task
31
+ after_jobs
35
32
 
36
- instance_exec(&block)
33
+ LocalCI::ExecContext.new(flow: self).instance_exec(&block)
37
34
  end
38
35
 
39
- def job(title, &block)
40
- task = "#{@task}:#{title}"
41
- spinner = job_spinner.register("[:spinner] #{title}")
42
-
43
- Rake::Task.define_task(task) do
44
- spinner.auto_spin
45
- start = Time.now
46
- instance_exec(&block)
47
- took = Time.now - start
48
- spinner.success("(#{took.round(2)}s)")
49
- rescue TTY::Command::ExitError => e
50
- spinner.error
51
- @failures << LocalCI::Failure.new(
52
- job: title,
53
- message: e.message
54
- )
36
+ def actions? = !!@actions
37
+
38
+ def actionless? = !actions?
39
+
40
+ def isolated? = LocalCI::Task["ci"].already_invoked
41
+
42
+ private
43
+
44
+ def setup_required_tasks
45
+ ci_task = LocalCI::Task["ci", "Run the CI suite"]
46
+
47
+ LocalCI::Task["ci:setup", "Setup the system to run CI"]
48
+ LocalCI::Task["ci:teardown", "Cleanup after the CI"]
49
+
50
+ ci_task.add_prerequisite "ci:setup"
51
+ ci_task.define do
52
+ LocalCI::Task["ci:teardown"].invoke
55
53
  end
54
+ end
55
+
56
+ def setup_flow_tasks
57
+ LocalCI::Task["#{@task}:setup"]
56
58
 
57
- @flow.prerequisites << task
59
+ LocalCI::Task.new("#{@task}:jobs", parallel_prerequisites: @parallel)
60
+
61
+ LocalCI::Task["#{@task}:teardown"]
62
+ LocalCI::Task["#{@task}:failure_check"]
63
+ LocalCI::Task[@task, @heading]
64
+
65
+ LocalCI::Task["#{@task}:setup"]
66
+ LocalCI::Task["#{@task}:setup"].add_prerequisite "ci:setup"
67
+
68
+ LocalCI::Task[@task].add_prerequisite "#{@task}:teardown"
69
+ LocalCI::Task["#{@task}:failure_check"].add_prerequisite "#{@task}:teardown"
70
+ LocalCI::Task["#{@task}:teardown"].add_prerequisite "#{@task}:jobs"
71
+ LocalCI::Task["#{@task}:jobs"].add_prerequisite "#{@task}:setup"
72
+
73
+ LocalCI::Task["ci"].add_prerequisite @task
58
74
  end
59
75
 
60
- def run(command)
61
- runner.run command
76
+ def setup_actionless_flow_tasks
77
+ LocalCI::Task.new("#{@task}:jobs", parallel_prerequisites: @parallel)
78
+ LocalCI::Task["#{@task}:failure_check"]
79
+
80
+ LocalCI::Task[@task].add_prerequisite "#{@task}:failure_check"
81
+ LocalCI::Task["#{@task}:failure_check"].add_prerequisite "#{@task}:jobs"
82
+ end
83
+
84
+ def after_jobs
85
+ LocalCI::Task[@task].define do
86
+ LocalCI::Task["#{@task}:teardown"].invoke
87
+ LocalCI::Task["ci:teardown"].invoke unless isolated? || actionless?
88
+
89
+ if @failures.any?
90
+ LocalCI::Task["ci:teardown"].invoke
91
+ @failures.each do |failure|
92
+ puts failure.display
93
+ end
94
+
95
+ abort LocalCI::Helper.pastel.red("#{@heading} failed, see CI.log for more.")
96
+ end
97
+ end
62
98
  end
63
99
  end
64
100
  end
@@ -0,0 +1,10 @@
1
+ module LocalCI
2
+ module Helper
3
+ def self.pastel = @pastel ||= Pastel.new(enabled: TTY::Color.support?)
4
+ def self.runner = @runner ||= TTY::Command.new(output: Logger.new("ci.log"))
5
+
6
+ def self.taskize(heading)
7
+ heading.downcase.gsub(/\s/, "_").gsub(/[^\w]/, "").to_sym
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,40 @@
1
+ module LocalCI
2
+ class Job
3
+ attr_accessor :flow, :name, :command, :block, :task, :spinner
4
+
5
+ def initialize(flow:, name:, command:, block:)
6
+ @flow = flow
7
+ @name = name
8
+ @command = command
9
+ @block = block
10
+ @task = "#{@flow.task}:jobs:#{LocalCI::Helper.taskize(name)}"
11
+
12
+ raise ArgumentError, "Must specify a block or command" unless command || block
13
+ @spinner = flow.spinner.register("[:spinner] #{name}")
14
+
15
+ ::Rake::Task.define_task(task) do
16
+ @spinner.auto_spin
17
+ start = Time.now
18
+
19
+ if block
20
+ LocalCI::ExecContext.new(flow: @flow).instance_exec(&block)
21
+ else
22
+ LocalCI::Helper.runner.run(*command)
23
+ end
24
+
25
+ took = Time.now - start
26
+ @spinner.success("(#{took.round(2)}s)")
27
+ rescue TTY::Command::ExitError => e
28
+ spinner.error
29
+ @flow.failures << LocalCI::Failure.new(
30
+ job: @name,
31
+ message: e.message
32
+ )
33
+ end
34
+
35
+ ::Rake::Task["#{@flow.task}:jobs"].prerequisites << task
36
+
37
+ ::Rake::Task[task].prerequisites << "#{@flow.task}:setup" if @flow.actions?
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ module LocalCI
2
+ module Rake
3
+ def self.setup(klass)
4
+ klass.send(:include, LocalCI::DSL)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ module LocalCI
2
+ class Task
3
+ extend Forwardable
4
+
5
+ def self.[](task, comment = nil)
6
+ new(task, comment: comment)
7
+ end
8
+
9
+ attr_accessor :task
10
+
11
+ def_delegators :@task, :comment=, :invoke, :already_invoked, :prerequisites
12
+
13
+ def initialize(task, comment: nil, parallel_prerequisites: false)
14
+ @parallel_prerequisites = parallel_prerequisites
15
+ klass.define_task(task) unless klass.task_defined?(task)
16
+ @task = klass[task]
17
+ @task.comment = comment if comment
18
+ end
19
+
20
+ def add_prerequisite(prerequisite)
21
+ return if prerequisites.include?(prerequisite)
22
+ prerequisites << prerequisite
23
+ end
24
+
25
+ def define(&block)
26
+ ::Rake::Task.define_task(@task.to_s) do
27
+ block.call
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def klass
34
+ @parallel_prerequisites ? ::Rake::MultiTask : ::Rake::Task
35
+ end
36
+ end
37
+ end
data/lib/local_ci.rb CHANGED
@@ -1,13 +1,14 @@
1
+ require "forwardable"
1
2
  require "logger"
2
3
  require "tty-command"
3
4
  require "tty-spinner"
4
5
  require "pastel"
5
6
 
7
+ require "local_ci/dsl"
8
+ require "local_ci/exec_context"
6
9
  require "local_ci/failure"
7
10
  require "local_ci/flow"
8
-
9
- module LocalCI
10
- def self.flow(task, heading, parallel: true, &block)
11
- LocalCI::Flow.new(task: task, heading: heading, parallel: parallel, block: block)
12
- end
13
- end
11
+ require "local_ci/helper"
12
+ require "local_ci/job"
13
+ require "local_ci/rake"
14
+ require "local_ci/task"
data/local_ci.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "local_ci"
3
- s.version = "0.0.1"
3
+ s.version = "0.0.2"
4
4
  s.summary = "Run CI locally"
5
5
  s.description = "A way to run CI locally but also able to run easily on your hosted CI"
6
6
  s.authors = ["Sean Earle"]
@@ -0,0 +1,15 @@
1
+ require "rspec"
2
+ require "rake"
3
+
4
+ require "local_ci"
5
+
6
+ require "support/dsl_klass"
7
+
8
+ RSpec.configure do |config|
9
+ config.after(:example) do
10
+ Rake::Task.clear
11
+
12
+ LocalCI::Helper.instance_variable_set(:@pastel, nil)
13
+ LocalCI::Helper.instance_variable_set(:@runner, nil)
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Support
2
+ class DSLKlass
3
+ include LocalCI::DSL
4
+ end
5
+ end
@@ -0,0 +1,85 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::DSL do
4
+ before do
5
+ @block = -> {}
6
+ end
7
+
8
+ describe ".flow" do
9
+ it "calls LocalCI::Flow" do
10
+ expect(LocalCI::Flow).to receive(:new).with(
11
+ name: :name,
12
+ heading: "heading",
13
+ parallel: "parallel",
14
+ block: @block
15
+ )
16
+
17
+ Support::DSLKlass.new.flow(:name, "heading", parallel: "parallel", &@block)
18
+ end
19
+
20
+ context "when only passed a heading" do
21
+ it "converts the heading to a sensible name name" do
22
+ expect(LocalCI::Flow).to receive(:new).with(
23
+ name: :my_cool_flow,
24
+ heading: "My Cool Flow!",
25
+ parallel: true,
26
+ block: @block
27
+ )
28
+
29
+ Support::DSLKlass.new.flow("My Cool Flow!", &@block)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe ".setup" do
35
+ it "calls LocalCI::Flow" do
36
+ expect(LocalCI::Flow).to receive(:new).with(
37
+ name: :setup,
38
+ heading: "Setup",
39
+ parallel: "parallel",
40
+ actions: false,
41
+ block: @block
42
+ )
43
+
44
+ Support::DSLKlass.new.setup(parallel: "parallel", &@block)
45
+ end
46
+
47
+ it "passes through the heading" do
48
+ expect(LocalCI::Flow).to receive(:new).with(
49
+ name: :setup,
50
+ heading: "My Cool Setup",
51
+ parallel: "parallel",
52
+ actions: false,
53
+ block: @block
54
+ )
55
+
56
+ Support::DSLKlass.new.setup("My Cool Setup", parallel: "parallel", &@block)
57
+ end
58
+ end
59
+
60
+ describe ".teardown" do
61
+ it "calls LocalCI::Flow" do
62
+ expect(LocalCI::Flow).to receive(:new).with(
63
+ name: :teardown,
64
+ heading: "Teardown",
65
+ parallel: "parallel",
66
+ actions: false,
67
+ block: @block
68
+ )
69
+
70
+ Support::DSLKlass.new.teardown(parallel: "parallel", &@block)
71
+ end
72
+
73
+ it "passes through the heading" do
74
+ expect(LocalCI::Flow).to receive(:new).with(
75
+ name: :teardown,
76
+ heading: "My Cool Teardown",
77
+ parallel: "parallel",
78
+ actions: false,
79
+ block: @block
80
+ )
81
+
82
+ Support::DSLKlass.new.teardown("My Cool Teardown", parallel: "parallel", &@block)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::ExecContext do
4
+ before do
5
+ @block = -> {}
6
+ @flow = double(
7
+ :flow,
8
+ task: "flow-task",
9
+ heading: "flow-heading"
10
+ )
11
+ @context = LocalCI::ExecContext.new(flow: @flow)
12
+ end
13
+
14
+ describe "#initialize" do
15
+ it "assigns the flow" do
16
+ expect(@context.flow).to eq(@flow)
17
+ end
18
+ end
19
+
20
+ describe "#run" do
21
+ it "runs the commands" do
22
+ expect(LocalCI::Helper.runner).to receive(:run)
23
+ .with("command", "arg1", "arg2")
24
+
25
+ @context.instance_exec {
26
+ run "command", "arg1", "arg2"
27
+ }
28
+ end
29
+ end
30
+
31
+ describe "#setup" do
32
+ it "creates a new flow" do
33
+ expect(LocalCI::Flow).to receive(:new)
34
+ .with(
35
+ name: "flow-task:setup",
36
+ heading: "flow-heading - Setup",
37
+ parallel: false,
38
+ actions: false,
39
+ block: @block
40
+ )
41
+
42
+ block = @block
43
+ @context.instance_exec {
44
+ setup(&block)
45
+ }
46
+ end
47
+
48
+ it "passes through heading and parallel" do
49
+ expect(LocalCI::Flow).to receive(:new)
50
+ .with(
51
+ name: "flow-task:setup",
52
+ heading: "Special Setup",
53
+ parallel: "parallel",
54
+ actions: false,
55
+ block: @block
56
+ )
57
+
58
+ block = @block
59
+ @context.instance_exec {
60
+ setup(
61
+ "Special Setup",
62
+ parallel: "parallel",
63
+ &block
64
+ )
65
+ }
66
+ end
67
+ end
68
+
69
+ describe "#teardown" do
70
+ it "creates a new flow" do
71
+ expect(LocalCI::Flow).to receive(:new)
72
+ .with(
73
+ name: "flow-task:teardown",
74
+ heading: "flow-heading - Teardown",
75
+ parallel: false,
76
+ actions: false,
77
+ block: @block
78
+ )
79
+
80
+ block = @block
81
+ @context.instance_exec {
82
+ teardown(&block)
83
+ }
84
+ end
85
+
86
+ it "passes through heading and parallel" do
87
+ expect(LocalCI::Flow).to receive(:new)
88
+ .with(
89
+ name: "flow-task:teardown",
90
+ heading: "Special Teardown",
91
+ parallel: "parallel",
92
+ actions: false,
93
+ block: @block
94
+ )
95
+
96
+ block = @block
97
+ @context.instance_exec {
98
+ teardown(
99
+ "Special Teardown",
100
+ parallel: "parallel",
101
+ &block
102
+ )
103
+ }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Failure do
4
+ describe "#message" do
5
+ it "returns information about the failure" do
6
+ pastel = double(:pastel)
7
+ expect(LocalCI::Helper).to receive(:pastel).and_return(pastel)
8
+ expect(pastel).to receive(:bold).and_return(pastel)
9
+ expect(pastel).to receive(:red).with("FAIL:")
10
+ .and_return("red")
11
+
12
+ failure = LocalCI::Failure.new(
13
+ job: "job",
14
+ message: "message"
15
+ )
16
+
17
+ expect(failure.message).to eq(<<~STR)
18
+ red job
19
+ message
20
+
21
+ STR
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,221 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Flow do
4
+ describe "#initialize" do
5
+ context "when creating parallel" do
6
+ it "call Rake::MultiTask" do
7
+ task = double(:task, prerequisites: [])
8
+ allow(task).to receive(:comment=)
9
+ allow(Rake::Task).to receive(:[]).and_return(task)
10
+ allow(Rake::Task).to receive(:define_task)
11
+ expect(Rake::MultiTask).to receive(:define_task).with("ci:parallel:jobs")
12
+
13
+ LocalCI::Flow.new(
14
+ name: "parallel",
15
+ heading: "heading",
16
+ parallel: true,
17
+ block: -> {}
18
+ )
19
+ end
20
+ end
21
+
22
+ context "when creating sequential" do
23
+ it "call Rake::Task" do
24
+ task = double(:task, prerequisites: [])
25
+ expect(task).to receive(:comment=).at_least(:once)
26
+ expect(Rake::Task).to receive(:[]).and_return(task).at_least(:once)
27
+ expect(Rake::Task).to receive(:define_task).with("ci:sequential:jobs").at_least(:once)
28
+ expect(Rake::Task).to receive(:define_task).at_least(:once)
29
+
30
+ LocalCI::Flow.new(
31
+ name: "sequential",
32
+ heading: "heading",
33
+ parallel: false,
34
+ block: -> {}
35
+ )
36
+ end
37
+ end
38
+
39
+ context "when actions is true" do
40
+ it "creates the expected tasks" do
41
+ LocalCI::Flow.new(
42
+ name: "actions",
43
+ heading: "heading",
44
+ parallel: true,
45
+ actions: true,
46
+ block: -> {}
47
+ )
48
+
49
+ expect(Rake::Task.task_defined?("ci")).to be(true)
50
+ expect(Rake::Task.task_defined?("ci:setup")).to be(true)
51
+ expect(Rake::Task.task_defined?("ci:actions")).to be(true)
52
+ expect(Rake::Task.task_defined?("ci:actions:setup")).to be(true)
53
+ expect(Rake::Task.task_defined?("ci:actions:jobs")).to be(true)
54
+ expect(Rake::Task.task_defined?("ci:actions:teardown")).to be(true)
55
+ expect(Rake::Task.task_defined?("ci:teardown")).to be(true)
56
+ end
57
+ end
58
+
59
+ context "when actions is false" do
60
+ it "creates the expected tasks" do
61
+ LocalCI::Flow.new(
62
+ name: "actionless",
63
+ heading: "heading",
64
+ parallel: true,
65
+ actions: false,
66
+ block: -> {}
67
+ )
68
+
69
+ expect(Rake::Task.task_defined?("ci")).to be(true)
70
+ expect(Rake::Task.task_defined?("ci:setup")).to be(true)
71
+ expect(Rake::Task.task_defined?("ci:actionless")).to be(true)
72
+ expect(Rake::Task.task_defined?("ci:actionless:setup")).to be(false)
73
+ expect(Rake::Task.task_defined?("ci:actionless:jobs")).to be(true)
74
+ expect(Rake::Task.task_defined?("ci:actionless:teardown")).to be(false)
75
+ expect(Rake::Task.task_defined?("ci:teardown")).to be(true)
76
+ end
77
+ end
78
+
79
+ it "gives tasks the right comments" do
80
+ LocalCI::Flow.new(
81
+ name: "test",
82
+ heading: "heading",
83
+ parallel: true,
84
+ block: -> {}
85
+ )
86
+
87
+ expect(Rake::Task["ci"].comment).to eq("Run the CI suite")
88
+ expect(Rake::Task["ci:setup"].comment).to eq("Setup the system to run CI")
89
+ expect(Rake::Task["ci:teardown"].comment).to eq("Cleanup after the CI")
90
+ expect(Rake::Task["ci:test"].comment).to eq("heading")
91
+ end
92
+
93
+ it "has the correct prerequisites for all tasks" do
94
+ LocalCI::Flow.new(
95
+ name: "test",
96
+ heading: "heading",
97
+ parallel: true,
98
+ block: -> {}
99
+ )
100
+
101
+ expect(Rake::Task["ci"].prerequisites).to eq(["ci:setup", "ci:test"])
102
+ expect(Rake::Task["ci:test"].prerequisites).to eq(["ci:test:teardown"])
103
+ expect(Rake::Task["ci:test:teardown"].prerequisites).to eq(["ci:test:jobs"])
104
+ expect(Rake::Task["ci:test:jobs"].prerequisites).to eq(["ci:test:setup"])
105
+ expect(Rake::Task["ci:test:setup"].prerequisites).to eq(["ci:setup"])
106
+ end
107
+
108
+ it "calls the expected jobs" do
109
+ allow(LocalCI::Job).to receive(:new)
110
+
111
+ job_1_block = -> {}
112
+
113
+ flow = LocalCI::Flow.new(
114
+ name: "test",
115
+ heading: "heading",
116
+ parallel: true,
117
+ block: -> {
118
+ job("Job 1", &job_1_block)
119
+ job("Job 2", "ls", "-la")
120
+ }
121
+ )
122
+
123
+ expect(LocalCI::Job).to have_received(:new).with(
124
+ flow: flow,
125
+ name: "Job 1",
126
+ command: [],
127
+ block: job_1_block
128
+ )
129
+
130
+ expect(LocalCI::Job).to have_received(:new).with(
131
+ flow: flow,
132
+ name: "Job 2",
133
+ command: ["ls", "-la"],
134
+ block: nil
135
+ )
136
+ end
137
+ end
138
+
139
+ describe "#after_jobs" do
140
+ before do
141
+ @flow = LocalCI::Flow.new(
142
+ name: "flow",
143
+ heading: "heading",
144
+ parallel: true,
145
+ block: -> {}
146
+ )
147
+
148
+ @ci = double(:ci, already_invoked: false)
149
+ allow(LocalCI::Task).to receive(:[]).with("ci").and_return(@ci)
150
+
151
+ @flow_teardown = double(:flow_teardown)
152
+ allow(@flow_teardown).to receive(:invoke)
153
+ allow(LocalCI::Task).to receive(:[]).with("ci:flow:teardown").and_return(@flow_teardown)
154
+
155
+ @teardown = double(:teardown)
156
+ allow(@teardown).to receive(:invoke)
157
+ allow(LocalCI::Task).to receive(:[]).with("ci:teardown").and_return(@teardown)
158
+ end
159
+
160
+ context "when there are no failures" do
161
+ it "runs ci:flow:teardown" do
162
+ expect(@flow_teardown).to receive(:invoke)
163
+
164
+ ::Rake::Task["ci:flow"].invoke
165
+ end
166
+
167
+ context "when run in isolation" do
168
+ it "runs ci:teardown" do
169
+ expect(@teardown).to receive(:invoke)
170
+
171
+ ::Rake::Task["ci:flow"].invoke
172
+ end
173
+ end
174
+
175
+ context "when not run in isolation" do
176
+ before do
177
+ allow(@ci).to receive(:already_invoked).and_return(true)
178
+ end
179
+
180
+ it "does not run ci:teardown" do
181
+ expect(@teardown).not_to receive(:invoke)
182
+
183
+ ::Rake::Task["ci:flow"].invoke
184
+ end
185
+ end
186
+ end
187
+
188
+ context "when there are failures" do
189
+ before do
190
+ @failure = double(:failure, display: "hi")
191
+ @flow.failures << @failure
192
+
193
+ allow(@flow).to receive(:abort)
194
+ end
195
+
196
+ it "runs ci:flow:teardown" do
197
+ expect(@flow_teardown).to receive(:invoke)
198
+
199
+ ::Rake::Task["ci:flow"].invoke
200
+ end
201
+
202
+ it "invokes ci:teardown" do
203
+ expect(@teardown).to receive(:invoke)
204
+
205
+ ::Rake::Task["ci:flow"].invoke
206
+ end
207
+
208
+ it "displays the failures" do
209
+ expect(@failure).to receive(:display)
210
+
211
+ ::Rake::Task["ci:flow"].invoke
212
+ end
213
+
214
+ it "aborts with a message" do
215
+ expect(@flow).to receive(:abort).with(/heading failed, see CI\.log for more\./)
216
+
217
+ ::Rake::Task["ci:flow"].invoke
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Helper do
4
+ describe ".pastel" do
5
+ it "returns a Pastel instance" do
6
+ expect(TTY::Color).to receive(:support?).and_return("support")
7
+ expect(Pastel).to receive(:new).with(enabled: "support")
8
+ .and_return("pastel")
9
+
10
+ expect(LocalCI::Helper.pastel).to eq("pastel")
11
+ end
12
+ end
13
+
14
+ describe ".runner" do
15
+ it "returns a TTY::Command instance" do
16
+ expect(Logger).to receive(:new).with("ci.log").and_return("logger")
17
+ expect(TTY::Command).to receive(:new).with(output: "logger")
18
+ .and_return("tty-command")
19
+
20
+ expect(LocalCI::Helper.runner).to eq("tty-command")
21
+ end
22
+ end
23
+
24
+ describe ".taskize" do
25
+ it "returns a task-ized version of a string" do
26
+ expect(LocalCI::Helper.taskize("Hello There!")).to eq(:hello_there)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,126 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Job do
4
+ before do
5
+ @flow = LocalCI::Flow.new(
6
+ name: "flow",
7
+ heading: "heading",
8
+ parallel: true,
9
+ block: -> {}
10
+ )
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "creates a new job with the right arguments" do
15
+ job = LocalCI::Job.new(
16
+ flow: @flow,
17
+ name: "The Job Name!",
18
+ command: "command",
19
+ block: "block"
20
+ )
21
+
22
+ expect(job.flow).to eq(@flow)
23
+ expect(job.name).to eq("The Job Name!")
24
+ expect(job.command).to eq("command")
25
+ expect(job.block).to eq("block")
26
+ expect(job.task).to eq("ci:flow:jobs:the_job_name")
27
+ end
28
+
29
+ context "when passed no block or command" do
30
+ it "raises an error" do
31
+ expect do
32
+ LocalCI::Job.new(
33
+ flow: @flow,
34
+ name: "name",
35
+ command: nil,
36
+ block: nil
37
+ )
38
+ end.to raise_error(ArgumentError, "Must specify a block or command")
39
+ end
40
+ end
41
+
42
+ it "defines a rake task" do
43
+ LocalCI::Job.new(
44
+ flow: @flow,
45
+ name: "The Job Name!",
46
+ command: "command",
47
+ block: "block"
48
+ )
49
+
50
+ expect(Rake::Task.task_defined?("ci:flow:jobs:the_job_name")).to be(true)
51
+ end
52
+
53
+ it "gives the rake task the right prerequisite" do
54
+ LocalCI::Job.new(
55
+ flow: @flow,
56
+ name: "The Job Name!",
57
+ command: "command",
58
+ block: "block"
59
+ )
60
+
61
+ expect(Rake::Task["ci:flow:jobs"].prerequisites).to include("ci:flow:jobs:the_job_name")
62
+ end
63
+ end
64
+
65
+ describe "task" do
66
+ context "when running a block" do
67
+ it "runs all the commands from the block" do
68
+ expect(LocalCI::Helper.runner).to receive(:run).with("command 1")
69
+ expect(LocalCI::Helper.runner).to receive(:run).with("command", "2")
70
+
71
+ LocalCI::Job.new(
72
+ flow: @flow,
73
+ name: "block task",
74
+ command: nil,
75
+ block: -> {
76
+ run "command 1"
77
+ run "command", "2"
78
+ }
79
+ )
80
+
81
+ ::Rake::Task["ci:flow:jobs:block_task"].invoke
82
+ end
83
+ end
84
+
85
+ context "when running one command inline" do
86
+ it "runs the commands" do
87
+ expect(LocalCI::Helper.runner).to receive(:run).with("command", "by", "argument")
88
+
89
+ LocalCI::Job.new(
90
+ flow: @flow,
91
+ name: "command task",
92
+ command: ["command", "by", "argument"],
93
+ block: nil
94
+ )
95
+ ::Rake::Task["ci:flow:jobs:command_task"].invoke
96
+ end
97
+ end
98
+
99
+ context "when the job fails" do
100
+ it "is recorded against the flow" do
101
+ expect(LocalCI::Helper.runner).to receive(:run).with("exit 1")
102
+ .and_raise(
103
+ TTY::Command::ExitError.new(
104
+ "exit 1",
105
+ double(
106
+ exit_status: 1,
107
+ err: "oops!",
108
+ out: ""
109
+ )
110
+ )
111
+ )
112
+
113
+ LocalCI::Job.new(
114
+ flow: @flow,
115
+ name: "Raises an Error",
116
+ command: "exit 1",
117
+ block: nil
118
+ )
119
+ ::Rake::Task["ci:flow:jobs:raises_an_error"].invoke
120
+
121
+ expect(@flow.failures.size).to eq(1)
122
+ expect(@flow.failures.first.job).to eq("Raises an Error")
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Rake do
4
+ describe ".setup" do
5
+ before do
6
+ @klass = double(:klass)
7
+ allow(@klass).to receive(:send)
8
+ end
9
+
10
+ it "includes the DSL" do
11
+ expect(@klass).to receive(:send).with(:include, LocalCI::DSL)
12
+
13
+ LocalCI::Rake.setup(@klass)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,114 @@
1
+ require "spec_helper"
2
+
3
+ describe LocalCI::Task do
4
+ describe "#initialize" do
5
+ context "with parallel preqresuites" do
6
+ it "creates a MultiTask" do
7
+ expect(::Rake::MultiTask).to receive(:[]).with("task")
8
+
9
+ LocalCI::Task.new("task", parallel_prerequisites: true)
10
+ end
11
+
12
+ context "with a comment" do
13
+ it "sets the comment" do
14
+ task = double(:task)
15
+ allow(::Rake::MultiTask).to receive(:[]).and_return(task)
16
+ expect(task).to receive(:comment=).with("comment")
17
+
18
+ LocalCI::Task.new("task", comment: "comment", parallel_prerequisites: true)
19
+ end
20
+ end
21
+
22
+ context "without a comment" do
23
+ it "does not set the comment" do
24
+ task = double(:task)
25
+ allow(::Rake::MultiTask).to receive(:[]).and_return(task)
26
+ expect(task).not_to receive(:comment=)
27
+
28
+ LocalCI::Task.new("task", parallel_prerequisites: true)
29
+ end
30
+ end
31
+ end
32
+
33
+ context "with sequential preqresuites" do
34
+ it "creates a Task" do
35
+ expect(::Rake::Task).to receive(:[]).with("task")
36
+
37
+ LocalCI::Task.new("task", parallel_prerequisites: false)
38
+ end
39
+
40
+ context "with a comment" do
41
+ it "sets the comment" do
42
+ task = double(:task)
43
+ allow(::Rake::Task).to receive(:[]).and_return(task)
44
+ expect(task).to receive(:comment=).with("comment")
45
+
46
+ LocalCI::Task.new("task", comment: "comment", parallel_prerequisites: false)
47
+ end
48
+ end
49
+
50
+ context "without a comment" do
51
+ it "does not set the comment" do
52
+ task = double(:task)
53
+ allow(::Rake::Task).to receive(:[]).and_return(task)
54
+ expect(task).not_to receive(:comment=)
55
+
56
+ LocalCI::Task.new("task", parallel_prerequisites: false)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ describe ".[]" do
63
+ it "creates a LocalCI::Task" do
64
+ expect(LocalCI::Task).to receive(:new).with(
65
+ "task",
66
+ comment: "comment"
67
+ )
68
+
69
+ LocalCI::Task["task", "comment"]
70
+ end
71
+ end
72
+
73
+ describe "#add_prerequisite" do
74
+ before do
75
+ @task = LocalCI::Task.new("task", parallel_prerequisites: true)
76
+ end
77
+
78
+ it "adds the prerequisite" do
79
+ expect(@task.prerequisites).to receive(:<<).with("prereq")
80
+
81
+ @task.add_prerequisite("prereq")
82
+ end
83
+
84
+ context "When it already has that prerequisite" do
85
+ it "doesn't add it again" do
86
+ expect(@task.prerequisites).not_to receive(:<<)
87
+ expect(@task).to receive(:prerequisites).and_return(["prereq"])
88
+
89
+ @task.add_prerequisite("prereq")
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#define" do
95
+ before do
96
+ @task = LocalCI::Task.new("task", parallel_prerequisites: true)
97
+ end
98
+
99
+ it "calls define_task" do
100
+ expect(::Rake::Task).to receive(:define_task).with("task")
101
+
102
+ @task.define {}
103
+ end
104
+
105
+ it "uses the passed in block as the task body" do
106
+ @block_run = false
107
+
108
+ @task.define { @block_run = true }
109
+
110
+ @task.invoke
111
+ expect(@block_run).to be(true)
112
+ end
113
+ end
114
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: local_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Earle
@@ -90,9 +90,25 @@ files:
90
90
  - Gemfile.lock
91
91
  - Rakefile
92
92
  - lib/local_ci.rb
93
+ - lib/local_ci/dsl.rb
94
+ - lib/local_ci/exec_context.rb
93
95
  - lib/local_ci/failure.rb
94
96
  - lib/local_ci/flow.rb
97
+ - lib/local_ci/helper.rb
98
+ - lib/local_ci/job.rb
99
+ - lib/local_ci/rake.rb
100
+ - lib/local_ci/task.rb
95
101
  - local_ci.gemspec
102
+ - spec/spec_helper.rb
103
+ - spec/support/dsl_klass.rb
104
+ - spec/unit/local_ci/dsl_spec.rb
105
+ - spec/unit/local_ci/exec_context_spec.rb
106
+ - spec/unit/local_ci/failure_spec.rb
107
+ - spec/unit/local_ci/flow_spec.rb
108
+ - spec/unit/local_ci/helper_spec.rb
109
+ - spec/unit/local_ci/job_spec.rb
110
+ - spec/unit/local_ci/rake_spec.rb
111
+ - spec/unit/local_ci/task_spec.rb
96
112
  homepage: https://github.com/HellRok/local_ci
97
113
  licenses:
98
114
  - MIT