citrus-core 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.
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