shipit-engine 0.27.1 → 0.28.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -1
  3. data/app/assets/stylesheets/_pages/_commits.scss +2 -0
  4. data/app/assets/stylesheets/_pages/_deploy.scss +6 -0
  5. data/app/controllers/shipit/api/release_statuses_controller.rb +22 -0
  6. data/app/controllers/shipit/api/stacks_controller.rb +6 -1
  7. data/app/controllers/shipit/commits_controller.rb +12 -1
  8. data/app/controllers/shipit/deploys_controller.rb +11 -0
  9. data/app/controllers/shipit/stacks_controller.rb +29 -1
  10. data/app/controllers/shipit/tasks_controller.rb +13 -1
  11. data/app/helpers/shipit/merge_status_helper.rb +2 -2
  12. data/app/helpers/shipit/stacks_helper.rb +10 -4
  13. data/app/jobs/shipit/perform_task_job.rb +1 -0
  14. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +41 -0
  15. data/app/models/shipit/command_line_user.rb +58 -0
  16. data/app/models/shipit/commit.rb +42 -2
  17. data/app/models/shipit/deploy.rb +31 -2
  18. data/app/models/shipit/deploy_spec/lerna_discovery.rb +43 -19
  19. data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -1
  20. data/app/models/shipit/rollback.rb +4 -2
  21. data/app/models/shipit/stack.rb +72 -15
  22. data/app/models/shipit/task.rb +30 -0
  23. data/app/models/shipit/undeployed_commit.rb +10 -1
  24. data/app/serializers/shipit/command_line_user_serializer.rb +4 -0
  25. data/app/views/layouts/shipit.html.erb +5 -1
  26. data/app/views/shipit/commits/_commit.html.erb +7 -2
  27. data/app/views/shipit/merge_status/_commit_count_warning.html.erb +1 -5
  28. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  29. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  30. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  31. data/app/views/shipit/merge_status/success.html.erb +1 -1
  32. data/app/views/shipit/stacks/show.html.erb +10 -1
  33. data/app/views/shipit/tasks/_task_output.html.erb +2 -2
  34. data/config/locales/en.yml +2 -1
  35. data/config/routes.rb +8 -1
  36. data/db/migrate/20190502020249_add_lock_author_id_to_commits.rb +5 -0
  37. data/lib/shipit.rb +14 -2
  38. data/lib/shipit/cast_value.rb +9 -0
  39. data/lib/shipit/command.rb +62 -16
  40. data/lib/shipit/line_buffer.rb +42 -0
  41. data/lib/shipit/version.rb +1 -1
  42. data/lib/tasks/shipit.rake +27 -0
  43. data/test/controllers/api/release_statuses_controller_test.rb +66 -0
  44. data/test/controllers/api/stacks_controller_test.rb +19 -0
  45. data/test/controllers/commits_controller_test.rb +30 -6
  46. data/test/controllers/deploys_controller_test.rb +51 -2
  47. data/test/controllers/tasks_controller_test.rb +24 -0
  48. data/test/dummy/db/schema.rb +2 -1
  49. data/test/dummy/db/seeds.rb +2 -0
  50. data/test/fixtures/shipit/check_runs.yml +11 -0
  51. data/test/fixtures/shipit/commits.yml +104 -0
  52. data/test/fixtures/shipit/stacks.yml +98 -3
  53. data/test/fixtures/shipit/tasks.yml +42 -0
  54. data/test/jobs/update_github_last_deployed_ref_job_test.rb +88 -0
  55. data/test/models/commits_test.rb +88 -1
  56. data/test/models/deploy_spec_test.rb +34 -6
  57. data/test/models/deploys_test.rb +308 -6
  58. data/test/models/rollbacks_test.rb +17 -11
  59. data/test/models/stacks_test.rb +217 -4
  60. data/test/models/tasks_test.rb +13 -0
  61. data/test/models/undeployed_commits_test.rb +62 -3
  62. data/test/test_helper.rb +0 -1
  63. data/test/unit/command_test.rb +55 -0
  64. data/test/unit/line_buffer_test.rb +20 -0
  65. metadata +142 -128
@@ -19,10 +19,14 @@ module Shipit
19
19
  refute @rollback.supports_rollback?
20
20
  end
21
21
 
22
- test "when a rollback succeed reverted commits are locked" do
23
- @stack.tasks.where.not(id: shipit_tasks(:shipit_complete).id).delete_all
22
+ test "when a rollback succeeds reverted commits are locked" do
23
+ @stack.tasks.where.not(id: shipit_tasks(:shipit_complete, :shipit).map(&:id)).delete_all
24
24
 
25
+ # Adjust the from/to commits of the two deploys to be in sequence (Any-3, 3-4)
26
+ to_revert_to = @stack.deploys.success.second_to_last
25
27
  deploy = @stack.deploys.success.last
28
+ to_revert_to.until_commit = deploy.since_commit
29
+ to_revert_to.save
26
30
  reverted_commit = deploy.until_commit
27
31
 
28
32
  @stack.commits.create!(
@@ -35,23 +39,25 @@ module Shipit
35
39
  )
36
40
 
37
41
  expected = [
38
- ['Revert "Merge pull request #7 from shipit-engine/yoloshipit"', false],
39
- ["whoami", false],
40
- ['fix all the things', false],
42
+ ['Revert "Merge pull request #7 from shipit-engine/yoloshipit"', false, nil],
43
+ ["whoami", false, nil],
44
+ ['fix all the things', false, nil],
41
45
  ]
42
- assert_equal expected, @stack.undeployed_commits.map { |c| [c.title, c.locked?] }
46
+ assert_equal(expected, @stack.undeployed_commits.map { |c| [c.title, c.locked?, c.lock_author_id] })
43
47
 
44
48
  rollback = deploy.trigger_revert
45
49
  rollback.run!
46
50
  rollback.complete!
47
51
 
52
+ user_id = reverted_commit.author.id
48
53
  expected = [
49
- ['Revert "Merge pull request #7 from shipit-engine/yoloshipit"', false],
50
- ["whoami", true],
51
- ['fix all the things', true],
52
- ['yoloshipit!', true],
54
+ ['Revert "Merge pull request #7 from shipit-engine/yoloshipit"', false, nil],
55
+ ["whoami", true, user_id],
56
+ ['fix all the things', true, user_id],
57
+ ['yoloshipit!', true, user_id],
53
58
  ]
54
- assert_equal expected, @stack.undeployed_commits.map { |c| [c.title, c.locked?] }
59
+
60
+ assert_equal(expected, @stack.undeployed_commits.map { |c| [c.title, c.locked?, c.lock_author_id] })
55
61
  end
56
62
  end
57
63
  end
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'securerandom'
2
3
 
3
4
  module Shipit
4
5
  class StacksTest < ActiveSupport::TestCase
@@ -132,6 +133,15 @@ module Shipit
132
133
  assert_instance_of Deploy, deploy
133
134
  end
134
135
 
136
+ test "#trigger_deploy doesn't enqueues a deploy job when run_now is provided" do
137
+ @stack.deploys.destroy_all
138
+ Deploy.any_instance.expects(:run_now!).once
139
+
140
+ last_commit = shipit_commits(:third)
141
+ deploy = @stack.trigger_deploy(last_commit, AnonymousUser.new, run_now: true)
142
+ assert_instance_of Deploy, deploy
143
+ end
144
+
135
145
  test "#trigger_deploy marks the deploy as `ignored_safeties` if the commit wasn't deployable" do
136
146
  last_commit = shipit_commits(:fifth)
137
147
  refute_predicate last_commit, :deployable?
@@ -262,6 +272,16 @@ module Shipit
262
272
  assert @stack.active_task?
263
273
  end
264
274
 
275
+ test ".run_deploy_in_foreground triggers a deploy" do
276
+ stack = Stack.create!(repo_owner: 'foo', repo_name: 'bar', environment: 'production')
277
+ commit = shipit_commits(:first)
278
+ stack.commits << commit
279
+
280
+ Stack.any_instance.expects(:trigger_deploy).with(anything, anything, env: {}, force: true, run_now: true)
281
+
282
+ Stack.run_deploy_in_foreground(stack: stack.to_param, revision: commit.sha)
283
+ end
284
+
265
285
  test "#active_task? is memoized" do
266
286
  assert_queries(1) do
267
287
  10.times { @stack.active_task? }
@@ -330,15 +350,19 @@ module Shipit
330
350
  end
331
351
 
332
352
  test "locking the stack triggers a webhook" do
333
- expect_hook(:lock, @stack, locked: true, stack: @stack) do
353
+ expect_hook(:lock, @stack, locked: true, lock_details: nil, stack: @stack) do
334
354
  @stack.update(lock_reason: "Just for fun", lock_author: shipit_users(:walrus))
335
355
  end
336
356
  end
337
357
 
338
358
  test "unlocking the stack triggers a webhook" do
339
- @stack.update(lock_reason: "Just for fun", lock_author: shipit_users(:walrus))
340
- expect_hook(:lock, @stack, locked: false, stack: @stack) do
341
- @stack.update(lock_reason: nil)
359
+ freeze_time do
360
+ time = Time.current
361
+ @stack.update(lock_reason: "Just for fun", lock_author: shipit_users(:walrus))
362
+ travel 1.day
363
+ expect_hook(:lock, @stack, locked: false, lock_details: {from: time, until: Time.current}, stack: @stack) do
364
+ @stack.update(lock_reason: nil)
365
+ end
342
366
  end
343
367
  end
344
368
 
@@ -493,6 +517,7 @@ module Shipit
493
517
  deploy = @stack.trigger_deploy(shipit_commits(:first), AnonymousUser.new)
494
518
  deploy.run!
495
519
  deploy.complete!
520
+ @stack.reload
496
521
 
497
522
  assert_predicate @stack, :deployable?
498
523
  assert_predicate @stack, :deployed_too_recently?
@@ -548,6 +573,39 @@ module Shipit
548
573
  end
549
574
  end
550
575
 
576
+ test "#trigger_continuous_delivery enqueues deployment ref update job" do
577
+ @stack = shipit_stacks(:shipit_canaries)
578
+ shipit_tasks(:canaries_running).delete
579
+
580
+ assert_no_enqueued_jobs(only: Shipit::UpdateGithubLastDeployedRefJob) do
581
+ assert_no_difference -> { Deploy.count } do
582
+ @stack.trigger_continuous_delivery
583
+ end
584
+ end
585
+
586
+ assert_enqueued_with(job: Shipit::UpdateGithubLastDeployedRefJob, args: [@stack]) do
587
+ @stack.last_active_task.complete!
588
+ end
589
+ end
590
+
591
+ test "#trigger_continuous_delivery executes ref update job with correct sha" do
592
+ @stack = shipit_stacks(:shipit_canaries)
593
+ shipit_tasks(:canaries_running).delete
594
+
595
+ assert_no_enqueued_jobs(only: Shipit::UpdateGithubLastDeployedRefJob) do
596
+ assert_no_difference -> { Deploy.count } do
597
+ @stack.trigger_continuous_delivery
598
+ end
599
+ end
600
+
601
+ desired_last_commit_sha = @stack.last_active_task.until_commit.sha
602
+ Shipit.github.api.expects(:update_ref).with(anything, anything, desired_last_commit_sha).returns("test")
603
+
604
+ perform_enqueued_jobs(only: Shipit::UpdateGithubLastDeployedRefJob) do
605
+ @stack.last_active_task.complete!
606
+ end
607
+ end
608
+
551
609
  test "#trigger_continuous_delivery trigger a deploy if all conditions are met" do
552
610
  @stack.tasks.delete_all
553
611
  assert_predicate @stack, :deployable?
@@ -669,5 +727,160 @@ module Shipit
669
727
  Rails.cache.clear
670
728
  refute_predicate @stack, :ci_enabled?
671
729
  end
730
+
731
+ test "#undeployed_commits returns list of commits newer than last deployed commit" do
732
+ @stack = shipit_stacks(:shipit_undeployed)
733
+ last_deployed_commit = @stack.last_deployed_commit
734
+ commits = @stack.undeployed_commits
735
+
736
+ assert_equal @stack.undeployed_commits_count, commits.size
737
+
738
+ commits.each { |c| assert c.id > last_deployed_commit.id }
739
+ end
740
+
741
+ test "#next_expected_commit_to_deploy returns nil if there is no deployable commit" do
742
+ commits = @stack.undeployed_commits
743
+
744
+ assert !commits.empty?
745
+ commits.each { |c| refute_predicate c, :deployable? }
746
+
747
+ assert_nil @stack.next_expected_commit_to_deploy(commits: commits)
748
+ end
749
+
750
+ test "#next_expected_commit_to_deploy returns nil if all deployable commits are active" do
751
+ @stack = shipit_stacks(:shipit_undeployed)
752
+ commits = @stack.undeployed_commits.select(&:active?)
753
+
754
+ assert !commits.empty?
755
+ commits.each { |c| assert_predicate c, :active? }
756
+
757
+ assert_nil @stack.next_expected_commit_to_deploy(commits: commits)
758
+ end
759
+
760
+ test "#next_expected_commit_to_deploy returns nil if there are no commits" do
761
+ assert_nil @stack.next_expected_commit_to_deploy(commits: [])
762
+ end
763
+
764
+ test "#next_expected_commit_to_deploy returns the most recent non-active deployable commit limited by maximum commits per deploy" do
765
+ @stack = shipit_stacks(:shipit_undeployed)
766
+ commits = @stack.undeployed_commits
767
+
768
+ assert !commits.empty?
769
+
770
+ most_recent_limited = @stack.next_expected_commit_to_deploy(commits: commits)
771
+ most_recent = commits.find { |c| !c.active? && c.deployable? }
772
+
773
+ assert most_recent.id > most_recent_limited.id
774
+ assert_equal @stack.maximum_commits_per_deploy, commits.find_index(most_recent_limited) + 1
775
+ end
776
+
777
+ test "#async_refresh_deployed_revision suppresses and logs raised exception" do
778
+ error_message = "Error message"
779
+
780
+ Rails.logger.expects(:warn).with("Failed to dispatch FetchDeployedRevisionJob: [StandardError] #{error_message}")
781
+ @stack.expects(:async_refresh_deployed_revision!).raises(StandardError.new(error_message))
782
+
783
+ @stack.async_refresh_deployed_revision
784
+ end
785
+
786
+ test "#lock_reverted_commits! locks all commits between the original and reverted commits" do
787
+ reverted_commit = @stack.undeployed_commits.first
788
+ revert_author = shipit_users(:bob)
789
+ generate_revert_commit(stack: @stack, reverted_commit: reverted_commit, author: revert_author)
790
+ @stack.reload
791
+
792
+ assert_equal(
793
+ [
794
+ ['Revert "whoami"', false, nil],
795
+ ["whoami", false, nil],
796
+ ["fix all the things", false, nil],
797
+ ],
798
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
799
+ )
800
+
801
+ @stack.lock_reverted_commits!
802
+ @stack.reload
803
+
804
+ assert_equal(
805
+ [
806
+ ['Revert "whoami"', false, nil],
807
+ ["whoami", true, revert_author.id],
808
+ ["fix all the things", false, nil],
809
+ ],
810
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
811
+ )
812
+ end
813
+
814
+ test "#lock_reverted_commits! is a no-op if the reverted commit has already shipped" do
815
+ reverted_commit = shipit_commits(:first)
816
+ revert_author = shipit_users(:bob)
817
+ generate_revert_commit(stack: @stack, reverted_commit: reverted_commit, author: revert_author)
818
+ @stack.reload
819
+
820
+ initial_state = [
821
+ ['Revert "lets go"', false, nil],
822
+ ["whoami", false, nil],
823
+ ["fix all the things", false, nil],
824
+ ]
825
+
826
+ assert_equal(
827
+ initial_state,
828
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
829
+ )
830
+
831
+ @stack.lock_reverted_commits!
832
+ @stack.reload
833
+
834
+ assert_equal(
835
+ initial_state,
836
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
837
+ )
838
+ end
839
+
840
+ test "#lock_reverted_commits! handles multiple reverts" do
841
+ first_reverted_commit = @stack.undeployed_commits.last
842
+ second_reverted_commit = @stack.undeployed_commits.first
843
+ first_revert_author = shipit_users(:bob)
844
+ second_revert_author = shipit_users(:walrus)
845
+ generate_revert_commit(stack: @stack, reverted_commit: first_reverted_commit, author: first_revert_author)
846
+ generate_revert_commit(stack: @stack, reverted_commit: second_reverted_commit, author: second_revert_author)
847
+ @stack.reload
848
+
849
+ assert_equal(
850
+ [
851
+ ['Revert "whoami"', false, nil],
852
+ ['Revert "fix all the things"', false, nil],
853
+ ["whoami", false, nil],
854
+ ["fix all the things", false, nil],
855
+ ],
856
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
857
+ )
858
+
859
+ @stack.lock_reverted_commits!
860
+ @stack.reload
861
+
862
+ assert_equal(
863
+ [
864
+ ['Revert "whoami"', false, nil],
865
+ ['Revert "fix all the things"', true, second_revert_author.id],
866
+ ["whoami", true, first_revert_author.id],
867
+ ["fix all the things", true, first_revert_author.id],
868
+ ],
869
+ @stack.undeployed_commits.map { |c| [c.message, c.locked, c.lock_author_id] },
870
+ )
871
+ end
872
+
873
+ private
874
+
875
+ def generate_revert_commit(stack:, reverted_commit:, author: reverted_commit.author)
876
+ stack.commits.create(
877
+ sha: SecureRandom.hex(20),
878
+ message: "Revert \"#{reverted_commit.message_header}\"",
879
+ author: author,
880
+ committer: author,
881
+ authored_at: Time.zone.now,
882
+ committed_at: Time.zone.now,
883
+ )
884
+ end
672
885
  end
673
886
  end
@@ -17,5 +17,18 @@ module Shipit
17
17
  task = shipit_tasks(:shipit_with_title_parsing_issue)
18
18
  assert_equal 'This task (title: Using the %{WRONG_VARIABLE_NAME}) cannot be shown due to an incorrect variable name. Check your shipit.yml file', task.title
19
19
  end
20
+
21
+ test "#write sends line-buffered output to task logger" do
22
+ task = shipit_tasks(:shipit)
23
+
24
+ mock_task_logger = mock.tap do |m|
25
+ m.expects(:info).with("[shipit-engine#1] hello").once
26
+ m.expects(:info).never
27
+ end
28
+
29
+ Shipit.stubs(:task_logger).returns(mock_task_logger)
30
+
31
+ task.write("hello\nworld")
32
+ end
20
33
  end
21
34
  end
@@ -4,7 +4,7 @@ module Shipit
4
4
  class UndeployedCommitsTest < ActiveSupport::TestCase
5
5
  setup do
6
6
  @real_commit = shipit_commits(:cyclimse_first)
7
- @commit = UndeployedCommit.new(@real_commit, 0)
7
+ @commit = UndeployedCommit.new(@real_commit, index: 0)
8
8
  @stack = @commit.stack
9
9
  end
10
10
 
@@ -34,11 +34,70 @@ module Shipit
34
34
  end
35
35
 
36
36
  test "#deploy_discouraged? returns true if the commit index is equal or bigger then the maximum commits per deploy" do
37
- @commit = UndeployedCommit.new(@real_commit, 2)
37
+ @commit = UndeployedCommit.new(@real_commit, index: 2)
38
38
  assert_equal 2, @stack.maximum_commits_per_deploy
39
39
  assert_predicate @commit, :deploy_discouraged?
40
40
  end
41
41
 
42
+ test "#expected_to_be_deployed? returns true if the stack has continuous deployment enabled, next expected commit to deploy id is greater or equals to the commit id and commit is not active" do
43
+ commit = shipit_commits(:undeployed_4)
44
+ next_expected_commit_to_deploy = commit.stack.next_expected_commit_to_deploy
45
+ undeployed_commit = UndeployedCommit.new(commit, index: 1, next_expected_commit_to_deploy: next_expected_commit_to_deploy)
46
+
47
+ refute_predicate next_expected_commit_to_deploy, :nil?
48
+ assert_predicate undeployed_commit.stack, :continuous_deployment
49
+ assert next_expected_commit_to_deploy.id >= undeployed_commit.id
50
+ refute_predicate undeployed_commit, :active?
51
+
52
+ assert_predicate undeployed_commit, :expected_to_be_deployed?
53
+ end
54
+
55
+ test "#expected_to_be_deployed? returns false if the stack has continuous deployment disabled" do
56
+ commit = shipit_commits(:cyclimse_first)
57
+ next_expected_commit_to_deploy = commit.stack.next_expected_commit_to_deploy
58
+ undeployed_commit = UndeployedCommit.new(commit, index: 1, next_expected_commit_to_deploy: next_expected_commit_to_deploy)
59
+
60
+ refute_predicate next_expected_commit_to_deploy, :nil?
61
+ refute_predicate undeployed_commit.stack, :continuous_deployment
62
+ assert next_expected_commit_to_deploy.id >= undeployed_commit.id
63
+ refute_predicate undeployed_commit, :active?
64
+
65
+ refute_predicate undeployed_commit, :expected_to_be_deployed?
66
+ end
67
+
68
+ test "#expected_to_be_deployed? returns false if the commit is part of the active task" do
69
+ commit = shipit_commits(:undeployed_3)
70
+ next_expected_commit_to_deploy = commit.stack.next_expected_commit_to_deploy
71
+ undeployed_commit = UndeployedCommit.new(commit, index: 1, next_expected_commit_to_deploy: next_expected_commit_to_deploy)
72
+
73
+ refute_predicate next_expected_commit_to_deploy, :nil?
74
+ assert_predicate undeployed_commit.stack, :continuous_deployment
75
+ assert next_expected_commit_to_deploy.id >= undeployed_commit.id
76
+ assert_predicate undeployed_commit, :active?
77
+
78
+ refute_predicate undeployed_commit, :expected_to_be_deployed?
79
+ end
80
+
81
+ test "#expected_to_be_deployed? returns false if there is no commit to deploy" do
82
+ commit = shipit_commits(:undeployed_3)
83
+ undeployed_commit = UndeployedCommit.new(commit, index: 1, next_expected_commit_to_deploy: nil)
84
+
85
+ refute_predicate undeployed_commit, :expected_to_be_deployed?
86
+ end
87
+
88
+ test "#expected_to_be_deployed? returns false if the commit has an id greater than next commit to deploy" do
89
+ commit = shipit_commits(:undeployed_7)
90
+ next_expected_commit_to_deploy = commit.stack.next_expected_commit_to_deploy
91
+ undeployed_commit = UndeployedCommit.new(commit, index: 1, next_expected_commit_to_deploy: next_expected_commit_to_deploy)
92
+
93
+ refute_predicate next_expected_commit_to_deploy, :nil?
94
+ assert_predicate undeployed_commit.stack, :continuous_deployment
95
+ assert undeployed_commit.id > next_expected_commit_to_deploy.id
96
+ refute_predicate undeployed_commit, :active?
97
+
98
+ refute_predicate undeployed_commit, :expected_to_be_deployed?
99
+ end
100
+
42
101
  test "#deploy_state returns `allowed` by default" do
43
102
  assert_equal 'allowed', @commit.deploy_state
44
103
  end
@@ -73,7 +132,7 @@ module Shipit
73
132
  blocking_commit.statuses.delete_all
74
133
  assert_predicate blocking_commit, :blocking?
75
134
 
76
- commit = UndeployedCommit.new(shipit_commits(:soc_third), 0)
135
+ commit = UndeployedCommit.new(shipit_commits(:soc_third), index: 0)
77
136
  assert_equal 'blocked', commit.deploy_state
78
137
  end
79
138
 
@@ -42,7 +42,6 @@ class ActiveSupport::TestCase
42
42
 
43
43
  setup do
44
44
  @routes = Shipit::Engine.routes
45
- Process.stubs(:kill)
46
45
  Shipit.github.api.stubs(:login).returns('shipit')
47
46
  end
48
47
 
@@ -66,5 +66,60 @@ module Shipit
66
66
  end
67
67
  assert_equal '/etc/passwd: Permission denied', error.message
68
68
  end
69
+
70
+ test 'sets code and message correctly on success' do
71
+ command = Command.new('true', chdir: '.')
72
+ assert_nil command.code
73
+ command.run
74
+ refute_predicate command, :running?
75
+ assert_predicate command.code, :zero?
76
+ assert_equal 'terminated successfully', command.termination_status
77
+ end
78
+
79
+ test 'sets code and message correctly on error' do
80
+ command = Command.new('false', chdir: '.')
81
+ assert_nil command.code
82
+ command.run
83
+ refute_predicate command, :running?
84
+ assert_predicate command.code, :nonzero?
85
+ assert_equal 'terminated with exit status 1', command.termination_status
86
+ end
87
+
88
+ test 'handles externally signalled commands correctly' do
89
+ command = Command.new('sleep 10', chdir: '.')
90
+ t = command_signaller_thread(command)
91
+ command.run
92
+ assert t.join, "subprocess wasn't signalled"
93
+ assert_predicate command, :signaled?
94
+ refute_predicate command, :running?
95
+ assert_nil command.code
96
+ assert_equal 'terminated with KILL signal', command.termination_status
97
+ end
98
+
99
+ test 'reports timedout command correctly' do
100
+ command = Command.new('sleep 10', chdir: '.', default_timeout: 0.5)
101
+ assert_raises(Command::TimedOut) { command.run }
102
+ assert_predicate command, :signaled?
103
+ refute_predicate command, :running?
104
+ assert_nil command.code
105
+ assert_equal 'timed out and terminated with INT signal', command.termination_status
106
+ end
107
+
108
+ private
109
+
110
+ def command_signaller_thread(command, signal: 'KILL')
111
+ Thread.new do
112
+ signalled = false
113
+ 20.times do
114
+ if command.running?
115
+ Process.kill(signal, command.pid)
116
+ signalled = true
117
+ break
118
+ end
119
+ sleep 0.1
120
+ end
121
+ signalled
122
+ end
123
+ end
69
124
  end
70
125
  end