citrus-core 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.citrus/config.rb +1 -1
  3. data/.gitignore +2 -1
  4. data/.travis.yml +4 -5
  5. data/README.md +1 -1
  6. data/Rakefile +3 -6
  7. data/citrus-core.gemspec +4 -2
  8. data/examples/bootstrap.rb +17 -17
  9. data/examples/payload.json +128 -1
  10. data/lib/citrus/core.rb +4 -18
  11. data/lib/citrus/core/build.rb +4 -2
  12. data/lib/citrus/core/cached_code_fetcher.rb +6 -5
  13. data/lib/citrus/core/configuration_loader.rb +2 -2
  14. data/lib/citrus/core/execute_build.rb +42 -0
  15. data/lib/citrus/core/exit_code.rb +21 -0
  16. data/lib/citrus/core/result.rb +7 -0
  17. data/lib/citrus/core/test_output.rb +19 -0
  18. data/lib/citrus/core/test_runner.rb +4 -6
  19. data/lib/citrus/core/version.rb +1 -1
  20. data/lib/citrus/core/workspace_builder.rb +5 -5
  21. data/lib/citrus/core/world.rb +21 -0
  22. data/spec/citrus/core/build_spec.rb +16 -0
  23. data/spec/{cached_code_fetcher_spec.rb → citrus/core/cached_code_fetcher_spec.rb} +17 -16
  24. data/spec/{changeset_spec.rb → citrus/core/changeset_spec.rb} +0 -0
  25. data/spec/citrus/core/clean_ruby_spec.rb +17 -0
  26. data/spec/{commit_changes_spec.rb → citrus/core/commit_changes_spec.rb} +0 -0
  27. data/spec/{commit_spec.rb → citrus/core/commit_spec.rb} +0 -0
  28. data/spec/{cofiguration_loader_spec.rb → citrus/core/configuration_loader_spec.rb} +1 -1
  29. data/spec/{cofiguration_spec.rb → citrus/core/configuration_spec.rb} +0 -0
  30. data/spec/{cofiguration_validator_spec.rb → citrus/core/configuration_validator_spec.rb} +0 -0
  31. data/spec/{execute_build_service_spec.rb → citrus/core/execute_build_spec.rb} +20 -15
  32. data/spec/{test_result_spec.rb → citrus/core/exit_code_spec.rb} +7 -6
  33. data/spec/{github_adapter_spec.rb → citrus/core/github_adapter_spec.rb} +1 -1
  34. data/spec/{publisher_spec.rb → citrus/core/publisher_spec.rb} +1 -1
  35. data/spec/{repository_spec.rb → citrus/core/repository_spec.rb} +0 -0
  36. data/spec/citrus/core/test_output_spec.rb +16 -0
  37. data/spec/citrus/core/test_runner_spec.rb +55 -0
  38. data/spec/citrus/core/workspace_builder_spec.rb +37 -0
  39. data/spec/citrus/core/world_spec.rb +11 -0
  40. data/spec/spec_helper.rb +6 -2
  41. metadata +81 -47
  42. data/examples/web.rb +0 -53
  43. data/lib/citrus/core/execute_build_service.rb +0 -30
  44. data/lib/citrus/core/test_result.rb +0 -24
  45. data/spec/build_spec.rb +0 -13
  46. data/spec/citrus_spec.rb +0 -18
  47. data/spec/test_runner_spec.rb +0 -44
  48. data/spec/workspace_builder_spec.rb +0 -55
@@ -0,0 +1,19 @@
1
+ module Citrus
2
+ module Core
3
+ class TestOutput
4
+
5
+ def initialize
6
+ @output = ""
7
+ end
8
+
9
+ def write(data)
10
+ @output << data
11
+ end
12
+
13
+ def read
14
+ @output
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,3 @@
1
- require 'stringio'
2
-
3
1
  module Citrus
4
2
  module Core
5
3
  class TestRunner
@@ -7,24 +5,24 @@ module Citrus
7
5
 
8
6
  include Publisher
9
7
 
10
- def start(configuration, path)
8
+ def start(build, configuration, path)
9
+ output = build.output
11
10
  process = ChildProcess.build(configuration.build_script)
12
11
  process.cwd = path.to_s
13
12
  r, w = IO.pipe
14
13
  process.io.stdout = process.io.stderr = w
15
14
  process.start
16
15
  w.close
17
- output = StringIO.new
18
16
  begin
19
17
  loop do
20
18
  chunk = r.readpartial(CHUNK_SIZE)
21
19
  output.write(chunk)
22
- publish(:output_received, chunk)
20
+ publish(:build_output_received, build, chunk)
23
21
  end
24
22
  rescue EOFError
25
23
  end
26
24
  process.wait
27
- TestResult.new(process.exit_code, output)
25
+ ExitCode.new(process.exit_code)
28
26
  end
29
27
 
30
28
  end
@@ -1,5 +1,5 @@
1
1
  module Citrus
2
2
  module Core
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -2,16 +2,16 @@ module Citrus
2
2
  module Core
3
3
  class WorkspaceBuilder
4
4
 
5
- attr_reader :root_path, :code_fetcher
5
+ attr_reader :build_root, :code_fetcher
6
6
 
7
- def initialize(root_path = Citrus::Core.build_root, code_fetcher = CachedCodeFetcher.new)
8
- @root_path = root_path
7
+ def initialize(build_root, code_fetcher)
8
+ @build_root = build_root
9
9
  @code_fetcher = code_fetcher
10
10
  end
11
11
 
12
12
  def create_workspace(build)
13
- path = root_path.join(partition, build.uuid)
14
- path.mkpath
13
+ path = File.join(build_root, partition, build.uuid)
14
+ FileUtils.mkpath(path)
15
15
  code_fetcher.fetch(build.changeset, path)
16
16
  path
17
17
  end
@@ -0,0 +1,21 @@
1
+ module Citrus
2
+ module Core
3
+ class World
4
+
5
+ attr_reader :root
6
+
7
+ def initialize(root)
8
+ @root = root
9
+ end
10
+
11
+ def cache_root
12
+ File.join(root, 'cache')
13
+ end
14
+
15
+ def build_root
16
+ File.join(root, 'builds')
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe Citrus::Core::Build do
5
+
6
+ subject { described_class.new(changeset, uuid, output) }
7
+
8
+ let(:changeset) { fake(:changeset) }
9
+ let(:uuid) { SecureRandom.uuid }
10
+ let(:output) { fake(:test_output) }
11
+
12
+ specify { expect(subject).to respond_to(:changeset) }
13
+ specify { expect(subject).to respond_to(:uuid) }
14
+ specify { expect(subject).to respond_to(:output) }
15
+
16
+ end
@@ -1,44 +1,47 @@
1
1
  require 'spec_helper'
2
+ require 'tmpdir'
3
+
4
+ class Digester
5
+ def hexdigest(input); input; end
6
+ end
2
7
 
3
8
  describe Citrus::Core::CachedCodeFetcher do
4
9
 
5
- subject { described_class.new(cache_root, vcs_adapter) }
10
+ subject { described_class.new(cache_root, vcs_adapter, digester) }
6
11
 
7
- let(:destination) { fake(:pathname) }
12
+ let(:destination) { '/destination' }
8
13
  let(:vcs_adapter) { fake(:git_adapter) }
14
+ let(:digester) { fake(:digester, hexdigest: 'dummy') }
9
15
  let(:changeset) { fake(:changeset, head: head_commit_sha, repository_url: repository_url) }
10
16
  let(:repository_url) { 'git://github.com/pawelpacana/citrus-core.git' }
11
17
  let(:head_commit_sha) { 'deadbeef' }
18
+ let(:cache_root) { Dir.mktmpdir('cache_root') }
19
+ let(:cache_dir) { File.join(cache_root, 'dummy') }
12
20
 
13
21
  context '#fetch' do
14
- include FakeFS::SpecHelpers
15
-
16
22
  context 'with empty cache' do
17
- let(:cache_root) { fake(:pathname) }
18
- let(:cache_dir) { fake(:pathname) }
19
23
 
20
24
  before do
21
- stub(cache_root).join(any_args) { cache_dir }
22
- stub(cache_dir).exist? { false }
23
25
  subject.fetch(changeset, destination)
24
26
  end
25
27
 
28
+ it 'should create cache dir' do
29
+ expect(File.exist?(cache_dir)).to be_true
30
+ end
31
+
26
32
  it 'should clone repository to cache dir' do
27
33
  expect(vcs_adapter).to have_received.clone_repository(repository_url, cache_dir)
28
34
  end
29
35
 
30
36
  it 'should clone updated cache to destination' do
37
+ mock(digester).hexdigest(repository_url) { cache_dir }
31
38
  expect(vcs_adapter).to have_received.clone_repository(cache_dir, destination)
32
39
  end
33
40
  end
34
41
 
35
42
  context 'with repository clone in cache' do
36
- let(:cache_root) { fake(:pathname) }
37
- let(:cache_dir) { fake(:pathname) }
38
-
39
43
  before do
40
- stub(cache_root).join(any_args) { cache_dir }
41
- stub(cache_dir).exist? { true }
44
+ FileUtils.mkpath(File.join(cache_dir, 'some_file'))
42
45
  subject.fetch(changeset, destination)
43
46
  end
44
47
 
@@ -52,12 +55,10 @@ describe Citrus::Core::CachedCodeFetcher do
52
55
  end
53
56
 
54
57
  context do
55
- let(:cache_root) { Pathname.new('/cache_root') }
56
-
57
58
  before { subject.fetch(changeset, destination) }
58
59
 
59
60
  it 'should create cache dir for repository' do
60
- expect(cache_root.children).to have(1).element
61
+ expect(Pathname.new(cache_root).children).to have(1).element
61
62
  end
62
63
 
63
64
  it 'should checkout head commit from changeset' do
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Ruby syntax' do
4
+
5
+ it 'is clean' do
6
+ expect(citrus_core_warnings).to eq([])
7
+ end
8
+
9
+ def citrus_core_warnings
10
+ warnings.select { |w| w =~ %r{lib/citrus/core} }
11
+ end
12
+
13
+ def warnings
14
+ `ruby -Ilib -w lib/citrus/core.rb 2>&1`.split("\n")
15
+ end
16
+
17
+ end
@@ -5,7 +5,7 @@ describe Citrus::Core::ConfigurationLoader do
5
5
  subject { described_class.new(validator) }
6
6
 
7
7
  let(:validator) { fake(:configuration_validator) }
8
- let(:test_root) { Pathname.new(File.dirname(__FILE__)) }
8
+ let(:test_root) { Pathname.new(File.dirname(__FILE__)).join('../../') }
9
9
  let(:repo_root) { test_root.join('fixtures/repo') }
10
10
 
11
11
  context '#load_from_path' do
@@ -2,56 +2,61 @@ require 'spec_helper'
2
2
 
3
3
  class Subscriber
4
4
  def build_started(build); end
5
- def build_failed(build, output); end
6
- def build_succeeded(build, output); end
5
+ def build_failed(build, result); end
6
+ def build_succeeded(build, result); end
7
7
  def build_aborted(build, reason); end
8
8
  end
9
9
 
10
- describe Citrus::Core::ExecuteBuildService do
10
+ describe Citrus::Core::ExecuteBuild do
11
11
 
12
12
  subject { described_class.new(workspace_builder, configuration_loader, test_runner) }
13
13
 
14
14
  let(:workspace_builder) { fake(:workspace_builder, create_workspace: path) }
15
- let(:test_runner) { fake(:test_runner, start: fake(:test_result, output: result_output)) }
16
- let(:build) { fake(:build) }
15
+ let(:test_runner) { fake(:test_runner, start: exit_code) }
16
+ let(:build) { fake(:build, output: test_output) }
17
17
  let(:configuration_loader) { fake(:configuration_loader, load_from_path: configuration) }
18
18
  let(:configuration) { fake(:configuration) }
19
19
  let(:path) { fake }
20
20
  let(:subscriber) { fake(:subscriber) }
21
- let(:success_result) { Citrus::Core::TestResult.new(0) }
22
- let(:failure_result) { Citrus::Core::TestResult.new(1) }
23
- let(:result_output) { StringIO.new }
21
+ let(:exit_code) { fake(:exit_code) }
22
+ let(:test_output) { fake(:test_output) }
24
23
 
25
24
  context '#start' do
26
25
  before { subject.add_subscriber(subscriber) }
27
26
 
28
27
  context do
29
- before { subject.start(build) }
30
-
31
28
  it 'should prepare workspace for build' do
29
+ subject.start(build)
32
30
  expect(workspace_builder).to have_received.create_workspace(build)
33
31
  end
34
32
 
35
33
  it 'should read build configuration from workspace' do
34
+ subject.start(build)
36
35
  expect(configuration_loader).to have_received.load_from_path(path)
37
36
  end
38
37
 
39
38
  it 'should execute build script' do
40
- expect(test_runner).to have_received.start(configuration, path)
39
+ subject.start(build)
40
+ expect(test_runner).to have_received.start(build, configuration, path)
41
41
  end
42
42
 
43
43
  it 'should publish build_started event when starting build' do
44
+ subject.start(build)
44
45
  expect(subscriber).to have_received.build_started(build)
45
46
  end
46
47
 
47
48
  it 'should publish build_succeeded event when build has succeeded' do
48
- stub(test_runner).start(any_args) { success_result }
49
- expect(subscriber).to have_received.build_succeeded(build, result_output)
49
+ stub(exit_code).success? { true }
50
+ stub(test_runner).start(any_args) { exit_code }
51
+ subject.start(build)
52
+ expect(subscriber).to have_received.build_succeeded(build, exit_code)
50
53
  end
51
54
 
52
55
  it 'should publish build_failed event when build has failed' do
53
- stub(test_runner).start(any_args) { failure_result }
54
- expect(subscriber).to have_received.build_failed(build, result_output)
56
+ stub(exit_code).failure? { true }
57
+ stub(test_runner).start(any_args) { exit_code }
58
+ subject.start(build)
59
+ expect(subscriber).to have_received.build_failed(build, exit_code)
55
60
  end
56
61
  end
57
62
 
@@ -1,6 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Citrus::Core::TestResult do
3
+ describe Citrus::Core::ExitCode do
4
+
5
+ it 'should expose exit code value' do
6
+ exit_code = 0
7
+ subject = described_class.new(exit_code)
8
+ expect(subject.value).to eq(exit_code)
9
+ end
4
10
 
5
11
  it 'should be successful for zero exit code value' do
6
12
  subject = described_class.new(0)
@@ -12,9 +18,4 @@ describe Citrus::Core::TestResult do
12
18
  expect(subject.failure?).to be_true
13
19
  end
14
20
 
15
- it 'should have IO like output' do
16
- subject = described_class.new(0)
17
- expect(subject.output).to respond_to(:rewind)
18
- end
19
-
20
21
  end
@@ -4,7 +4,7 @@ describe Citrus::Core::GithubAdapter do
4
4
 
5
5
  subject { described_class.new }
6
6
 
7
- let(:push_data) { Pathname.new(File.dirname(__FILE__)).join('fixtures/github_push_data.json').read }
7
+ let(:push_data) { Pathname.new(File.dirname(__FILE__)).join('../../fixtures/github_push_data.json').read }
8
8
 
9
9
  context '#create_changeset_from_push_data' do
10
10
  let(:changeset) { subject.create_changeset_from_push_data(push_data) }
@@ -20,7 +20,7 @@ describe Citrus::Core::Publisher do
20
20
  expect {
21
21
  subject.add_subscriber(subscriber)
22
22
  subject.publish(:foo)
23
- }.to_not raise_error(NoMethodError)
23
+ }.to_not raise_error
24
24
  end
25
25
 
26
26
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Citrus::Core::TestOutput do
4
+
5
+ let(:test_output) { described_class.new }
6
+
7
+ specify { expect(test_output).to respond_to(:read) }
8
+ specify { expect(test_output).to respond_to(:write) }
9
+
10
+ it 'should accumulate test output' do
11
+ chunks = %w(kaka dudu)
12
+ chunks.each { |c| test_output.write(c) }
13
+ expect(test_output.read).to eq(chunks.join)
14
+ end
15
+
16
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ class Subscriber
4
+ def build_output_received(build, data); end
5
+ end
6
+
7
+ describe Citrus::Core::TestRunner do
8
+
9
+ subject { described_class.new }
10
+
11
+ let(:configuration) { fake(:configuration, build_script: 'hostname') }
12
+ let(:path) { Pathname.new('/') }
13
+ let(:process) { fake(io: process_io, exit_code: exit_code) { ChildProcess::AbstractProcess } }
14
+ let(:process_io) { fake { ChildProcess::AbstractIO } }
15
+ let(:exit_code) { fake(:fixnum) }
16
+ let(:subscriber) { fake(:subscriber) }
17
+ let(:build) { fake(:build, output: build_output) }
18
+ let(:build_output) { fake(:test_output) }
19
+
20
+ context '#start' do
21
+ context do
22
+ before { stub(ChildProcess).build(configuration.build_script) { process } }
23
+
24
+ it 'spawns child process' do
25
+ subject.start(build, configuration, path)
26
+ expect(process).to have_received.start
27
+ end
28
+
29
+ it 'should return test result' do
30
+ expect(subject.start(build, configuration, path)).to be_kind_of(Citrus::Core::ExitCode)
31
+ end
32
+
33
+ it 'should wait for process to finish' do
34
+ subject.start(build, configuration, path)
35
+ expect(process).to have_received.wait
36
+ end
37
+ end
38
+
39
+ it 'should publish build_output_received event when process produced output' do
40
+ subject.add_subscriber(subscriber)
41
+ subject.start(build, configuration, path)
42
+ expect(subscriber).to have_received.build_output_received(build, `hostname`)
43
+ end
44
+
45
+ it 'should write process output to build' do
46
+ subject.start(build, configuration, path)
47
+ expect(build_output).to have_received.write(`hostname`)
48
+ end
49
+ end
50
+
51
+ it 'should allow adding subscribers' do
52
+ expect(subject).to respond_to(:add_subscriber)
53
+ end
54
+
55
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+
4
+ describe Citrus::Core::WorkspaceBuilder do
5
+
6
+ subject { described_class.new(build_root, code_fetcher) }
7
+
8
+ let(:build) { fake(:build, uuid: uuid, changeset: changeset) }
9
+ let(:uuid) { SecureRandom.uuid }
10
+ let(:changeset) { fake(:changeset) }
11
+ let(:code_fetcher) { fake(:cached_code_fetcher) }
12
+ let(:build_root) { Dir.mktmpdir('build_root') }
13
+
14
+ context '#create_workspace' do
15
+ let(:result) { subject.create_workspace(build) }
16
+
17
+ it 'should create build directory under build root' do
18
+ expect(result).to start_with(build_root)
19
+ end
20
+
21
+ it 'should create workspace in directories partitioned by date' do
22
+ expect(File.dirname(result)).to match(%r{/\d{4}/\d{2}/\d{2}\Z})
23
+ end
24
+
25
+ it 'should create workspace named after builds uuid' do
26
+ expect(File.basename(result)).to eql(uuid)
27
+ end
28
+
29
+ it 'should fetch code needed for build into workspace' do
30
+ stub(Time).now { Time.at(0) }
31
+ result
32
+ expect(code_fetcher).to have_received.fetch(changeset, "#{build_root}/1970/01/01/#{uuid}")
33
+ end
34
+
35
+ end
36
+
37
+ end