shipit-engine 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
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