tinyci 0.4.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/runner'
2
4
  require 'tinyci/subprocesses'
3
5
  require 'tinyci/git_utils'
@@ -7,82 +9,82 @@ module TinyCI
7
9
  # Manages the execution of test jobs. Responsible for deciding which
8
10
  # commits need to be built and tested. Also manages the pidfile. This
9
11
  # is the main entrypoint for TinyCI.
10
- #
12
+ #
11
13
  # @attr_reader [String] working_dir The working directory to execute against
12
14
  class Scheduler
13
15
  include Subprocesses
14
16
  include Logging
15
17
  include GitUtils
16
-
18
+
17
19
  attr_reader :working_dir
18
-
20
+
19
21
  # Constructor, allows injection of configuration and custom {Runner} class.
20
22
  # Config params are passed to {Runner} instances.
21
- #
23
+ #
22
24
  # @param working_dir [String] The working directory to execute against
23
25
  # @param logger [Logger] Logger object
24
26
  # @param commit [String] specific git object to run against
25
27
  # @param runner_class [TinyCI::Runner] Injection of {Runner} dependency
26
28
  def initialize(
27
- working_dir: nil,
28
- logger: nil,
29
- commit: nil,
30
- runner_class: Runner
31
- )
32
-
29
+ working_dir: nil,
30
+ logger: nil,
31
+ commit: nil,
32
+ runner_class: Runner
33
+ )
34
+
33
35
  @working_dir = working_dir || repo_root
34
36
  @logger = logger
35
37
  @runner_class = runner_class
36
38
  @commit = commit
37
39
  end
38
-
40
+
39
41
  # Runs the TinyCI system against the relevant commits. Also sets up the pidfile.
40
- #
42
+ #
41
43
  # @return [Boolean] `true` if all commits built and tested successfully, `false` otherwise
42
44
  def run!
43
45
  pid = PidFile.new(pidfile: 'tinyci.pid', piddir: @working_dir)
44
-
46
+
45
47
  result = if @commit
46
- run_commit get_commit @commit
47
- else
48
- run_all_commits
49
- end
50
-
48
+ run_commit get_commit @commit
49
+ else
50
+ run_all_commits
51
+ end
52
+
51
53
  pid.release
52
-
54
+
53
55
  result
54
56
  end
55
-
57
+
56
58
  private
57
-
59
+
58
60
  # Git objects to be executed against, all those without a tinyci tag
59
- #
61
+ #
60
62
  # @return [Array<String>] the sha1 hashes in reverse order of creation time
61
- def get_commits
63
+ def retrieve_commits
62
64
  log = execute(git_cmd('log', '--notes=tinyci*', '--format=%H %ct %N§§§', '--reverse'))
63
- lines = log.split("§§§")
64
-
65
- lines.map {|l| format_commit_data(l)}.select {|c| c[:result].nil?}
65
+ lines = log.split('§§§')
66
+
67
+ lines.map { |l| format_commit_data(l) }.select { |c| c[:result].nil? }
66
68
  end
67
-
69
+
68
70
  def get_commit(sha)
69
- data = execute(git_cmd('show', '--quiet', '--notes=tinyci*', "--format=%H %ct", sha))
70
-
71
+ data = execute(git_cmd('show', '--quiet', '--notes=tinyci*', '--format=%H %ct', sha))
72
+
71
73
  format_commit_data(data)
72
74
  end
73
-
75
+
74
76
  # Instantiates {Runner} for a given git object, runs it, and stores the result
75
77
  def run_commit(commit)
76
78
  result = @runner_class.new(
77
- working_dir: @working_dir,
79
+ working_dir: @working_dir,
78
80
  commit: commit[:sha],
79
81
  time: commit[:time],
80
82
  logger: @logger
81
83
  ).run!
82
-
84
+
83
85
  set_result(commit, result)
84
86
  end
85
-
87
+
86
88
  def format_commit_data(data)
87
89
  parts = data.split(' ')
88
90
  {
@@ -91,22 +93,22 @@ module TinyCI
91
93
  result: parts[2]
92
94
  }
93
95
  end
94
-
96
+
95
97
  # Repeatedly gets the list of eligable commits and runs TinyCI against them until there are no more remaining
96
98
  def run_all_commits
97
- commits = get_commits
98
-
99
- until commits.empty? do
100
- commits.each {|c| run_commit(c)}
101
-
102
- commits = get_commits
99
+ commits = retrieve_commits
100
+
101
+ until commits.empty?
102
+ commits.each { |c| run_commit(c) }
103
+
104
+ commits = retrieve_commits
103
105
  end
104
106
  end
105
-
107
+
106
108
  # Stores the result in a git note
107
109
  def set_result(commit, result)
108
110
  result_message = result ? 'success' : 'failure'
109
-
111
+
110
112
  execute git_cmd('notes', '--ref', 'tinyci-result', 'add', '-m', result_message, commit[:sha])
111
113
  end
112
114
  end
@@ -1,44 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open3'
4
+ require 'English'
2
5
  require 'tinyci/logging'
3
6
 
4
7
  module TinyCI
5
8
  # Methods for executing subprocesses in various ways and collecting the results.
6
9
  module Subprocesses
7
-
8
10
  # Synchronously execute a command as a subprocess and return the output.
9
- #
11
+ #
10
12
  # @param [Array<String>] command The command line
11
13
  # @param [String] label A label for debug and logging purposes
12
- #
14
+ #
13
15
  # @return [String] The output of the command
14
16
  # @raise [SubprocessError] if the subprocess returns status > 0
15
17
  def execute(*command, label: nil)
16
- output, status = Open3.capture2(*command.flatten)
17
-
18
+ stdout, stderr, status = Open3.capture3(*command.flatten)
19
+
18
20
  log_debug caller[0]
19
21
  log_debug "CMD: #{command.join(' ')}"
20
- log_debug "OUT: #{output}"
21
-
22
+ log_debug "OUT: #{stdout}"
23
+ log_debug "ERR: #{stderr}"
24
+
22
25
  unless status.success?
23
- log_error output
26
+ log_error stdout
27
+ log_error stderr
24
28
  raise SubprocessError.new(label, command.join(' '), status)
25
29
  end
26
-
27
- output.chomp
30
+
31
+ stdout.chomp
28
32
  end
29
-
33
+
30
34
  # Synchronously execute a chain multiple commands piped into each other as a
31
35
  # subprocess and return the output.
32
- #
36
+ #
33
37
  # @param [Array<Array<String>>] commands The command lines
34
38
  # @param [String] label A label for debug and logging purposes
35
- #
39
+ #
36
40
  # @return [String] The output of the command
37
41
  # @raise [SubprocessError] if the subprocess returns status > 0
38
42
  def execute_pipe(*commands, label: nil)
39
43
  stdout, waiters = Open3.pipeline_r(*commands)
40
44
  output = stdout.read
41
-
45
+
42
46
  waiters.each_with_index do |waiter, i|
43
47
  status = waiter.value
44
48
  unless status.success?
@@ -46,67 +50,72 @@ module TinyCI
46
50
  raise SubprocessError.new(label, commands[i].join(' '), status)
47
51
  end
48
52
  end
49
-
53
+
50
54
  output.chomp
51
55
  end
52
-
56
+
53
57
  # Synchronously execute a command as a subprocess and and stream the output
54
58
  # to `STDOUT`
55
- #
59
+ #
56
60
  # @param [Array<String>] command The command line
57
61
  # @param [String] label A label for debug and logging purposes
58
62
  # @param [String] pwd Optionally specify a different working directory in which to execute the command
59
- #
63
+ #
60
64
  # @return [TrueClass] `true` if the command executed successfully
61
65
  # @raise [SubprocessError] if the subprocess returns status > 0
62
66
  def execute_stream(*command, label: nil, pwd: nil)
63
67
  opts = {}
64
68
  opts[:chdir] = pwd unless pwd.nil?
65
-
69
+
66
70
  log_debug "CMD: #{command.join(' ')}"
67
-
71
+
68
72
  Open3.popen2e(command.join(' '), opts) do |stdin, stdout_and_stderr, wait_thr|
69
73
  stdin.close
70
-
74
+
71
75
  until stdout_and_stderr.closed? || stdout_and_stderr.eof?
72
76
  line = stdout_and_stderr.gets
73
77
  log_info line.chomp
74
78
  $stdout.flush
75
79
  end
76
-
77
- unless wait_thr.value.success?
80
+
81
+ unless wait_thr.value.success?
78
82
  raise SubprocessError.new(label, command.join(' '), wait_thr.value)
79
83
  end
84
+
85
+ ensure
80
86
  stdout_and_stderr.close
81
87
  end
82
-
88
+
83
89
  true
84
90
  end
85
-
91
+
92
+ def execute_and_return_status(command)
93
+ system(*command, out: File::NULL, err: File::NULL)
94
+
95
+ $CHILD_STATUS
96
+ end
97
+
86
98
  # An error raised when any of the {Subprocesses} methods fail
87
- #
99
+ #
88
100
  # @attr_reader [Integer] status The return code of the process
89
101
  # @attr_reader [String] command The command used to spawn the process
90
102
  class SubprocessError < RuntimeError
91
103
  attr_reader :status
92
104
  attr_reader :command
93
-
105
+
94
106
  def initialize(label, command, status, message = "#{label}: `#{command}` failed with status #{status.exitstatus}")
95
107
  @status = status
96
108
  @command = command
97
109
  super(message)
98
110
  end
99
111
  end
100
-
101
- private
102
-
112
+
103
113
  def self.included(base)
104
114
  base.include TinyCI::Logging
105
115
  end
106
-
116
+
107
117
  def self.extended(base)
108
118
  base.extend TinyCI::Logging
109
119
  end
110
-
111
120
  end
112
121
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TinyCI
2
4
  module Symbolize
3
5
  # recursively make all keys of `hash` into symbols
@@ -7,7 +9,7 @@ module TinyCI
7
9
  hash.each { |key, value| h[key.to_sym] = map_value(value) }
8
10
  end
9
11
  end
10
-
12
+
11
13
  def map_value(thing)
12
14
  case thing
13
15
  when Hash
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/executor'
2
4
 
3
5
  module TinyCI
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/executor'
2
4
 
3
5
  module TinyCI
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TinyCI
2
- VERSION = "0.4.2"
4
+ VERSION = '0.5'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'redcarpet'
2
4
 
3
5
  # This file fixes the mismatch between how github handles links to other
@@ -27,7 +29,13 @@ class CompatMarkdown
27
29
 
28
30
  def initialize(text)
29
31
  renderer = YARD::Templates::Helpers::HtmlHelper::MDLinkRenderer.new
30
- markdown = Redcarpet::Markdown.new(renderer, no_intra_emphasis: true, gh_blockcode: true, fenced_code_blocks: true, autolink: true)
32
+ opts = {
33
+ no_intra_emphasis: true,
34
+ gh_blockcode: true,
35
+ fenced_code_blocks: true,
36
+ autolink: true
37
+ }
38
+ markdown = Redcarpet::Markdown.new(renderer, opts)
31
39
  @to_html = markdown.render(text)
32
40
  end
33
41
  end
@@ -1,50 +1,57 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'tinyci/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "tinyci"
8
+ spec.name = 'tinyci'
8
9
  spec.version = TinyCI::VERSION
9
- spec.authors = ["Jonathan Davies"]
10
- spec.email = ["jonnie@cleverna.me"]
10
+ spec.authors = ['Jonathan Davies']
11
+ spec.email = ['jonnie@cleverna.me']
11
12
 
12
- desc = "A minimal Continuous Integration system, written in ruby, powered by git"
13
+ desc = 'A minimal Continuous Integration system, written in ruby, powered by git'
13
14
  spec.summary = desc
14
15
  spec.description = desc
15
- spec.homepage = "https://github.com/JonnieCache/tinyci"
16
- spec.license = "MIT"
16
+ spec.homepage = 'https://github.com/JonnieCache/tinyci'
17
+ spec.license = 'MIT'
17
18
 
18
19
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
20
  # to allow pushing to a single host or delete this section to allow pushing to any host.
20
21
  if spec.respond_to?(:metadata)
21
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
23
  else
23
- raise "RubyGems 2.0 or newer is required to protect against " \
24
- "public gem pushes."
24
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
25
+ 'public gem pushes.'
25
26
  end
26
27
 
27
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
29
  f.match(%r{^(test|spec|features)/})
29
30
  end
30
- spec.executables = ["tinyci"]
31
- spec.require_paths = ["lib"]
31
+ spec.executables = ['tinyci']
32
+ spec.require_paths = ['lib']
32
33
 
33
34
  LOGO = File.read(File.expand_path('lib/tinyci/logo.txt', __dir__))
34
35
 
35
36
  spec.post_install_message = (LOGO % TinyCI::VERSION) + "\n"
36
37
 
37
- spec.add_development_dependency 'rspec', '>= 3.8.0'
38
+ spec.add_dependency 'file-tail'
39
+ spec.add_dependency 'git_clone_url'
40
+ spec.add_dependency 'net-ssh'
41
+
42
+ spec.add_development_dependency 'awesome_print'
38
43
  spec.add_development_dependency 'barrier'
39
- spec.add_development_dependency 'rake'
40
- spec.add_development_dependency 'yard'
41
- spec.add_development_dependency 'redcarpet'
44
+ spec.add_development_dependency 'fuubar'
45
+ spec.add_development_dependency 'guard-rspec'
46
+ spec.add_development_dependency 'pry'
42
47
  spec.add_development_dependency 'pry-byebug'
43
48
  spec.add_development_dependency 'pry-doc'
44
- spec.add_development_dependency 'pry'
45
- spec.add_development_dependency 'guard-rspec'
46
- spec.add_development_dependency 'fuubar'
47
- spec.add_development_dependency 'simplecov'
49
+ spec.add_development_dependency 'rake'
50
+ spec.add_development_dependency 'redcarpet'
51
+ spec.add_development_dependency 'rspec', '>= 3.8.0'
48
52
  spec.add_development_dependency 'rspec-nc'
53
+ spec.add_development_dependency 'rubocop'
54
+ spec.add_development_dependency 'simplecov'
49
55
  spec.add_development_dependency 'terminal-notifier', '1.7.2'
56
+ spec.add_development_dependency 'yard'
50
57
  end
metadata CHANGED
@@ -1,29 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tinyci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Davies
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-28 00:00:00.000000000 Z
11
+ date: 2020-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: file-tail
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.8.0
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: git_clone_url
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-ssh
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
20
62
  type: :development
21
63
  prerelease: false
22
64
  version_requirements: !ruby/object:Gem::Requirement
23
65
  requirements:
24
66
  - - ">="
25
67
  - !ruby/object:Gem::Version
26
- version: 3.8.0
68
+ version: '0'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: barrier
29
71
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +81,7 @@ dependencies:
39
81
  - !ruby/object:Gem::Version
40
82
  version: '0'
41
83
  - !ruby/object:Gem::Dependency
42
- name: rake
84
+ name: fuubar
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - ">="
@@ -53,7 +95,7 @@ dependencies:
53
95
  - !ruby/object:Gem::Version
54
96
  version: '0'
55
97
  - !ruby/object:Gem::Dependency
56
- name: yard
98
+ name: guard-rspec
57
99
  requirement: !ruby/object:Gem::Requirement
58
100
  requirements:
59
101
  - - ">="
@@ -67,7 +109,7 @@ dependencies:
67
109
  - !ruby/object:Gem::Version
68
110
  version: '0'
69
111
  - !ruby/object:Gem::Dependency
70
- name: redcarpet
112
+ name: pry
71
113
  requirement: !ruby/object:Gem::Requirement
72
114
  requirements:
73
115
  - - ">="
@@ -109,7 +151,7 @@ dependencies:
109
151
  - !ruby/object:Gem::Version
110
152
  version: '0'
111
153
  - !ruby/object:Gem::Dependency
112
- name: pry
154
+ name: rake
113
155
  requirement: !ruby/object:Gem::Requirement
114
156
  requirements:
115
157
  - - ">="
@@ -123,7 +165,7 @@ dependencies:
123
165
  - !ruby/object:Gem::Version
124
166
  version: '0'
125
167
  - !ruby/object:Gem::Dependency
126
- name: guard-rspec
168
+ name: redcarpet
127
169
  requirement: !ruby/object:Gem::Requirement
128
170
  requirements:
129
171
  - - ">="
@@ -137,7 +179,21 @@ dependencies:
137
179
  - !ruby/object:Gem::Version
138
180
  version: '0'
139
181
  - !ruby/object:Gem::Dependency
140
- name: fuubar
182
+ name: rspec
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: 3.8.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: 3.8.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: rspec-nc
141
197
  requirement: !ruby/object:Gem::Requirement
142
198
  requirements:
143
199
  - - ">="
@@ -151,7 +207,7 @@ dependencies:
151
207
  - !ruby/object:Gem::Version
152
208
  version: '0'
153
209
  - !ruby/object:Gem::Dependency
154
- name: simplecov
210
+ name: rubocop
155
211
  requirement: !ruby/object:Gem::Requirement
156
212
  requirements:
157
213
  - - ">="
@@ -165,7 +221,7 @@ dependencies:
165
221
  - !ruby/object:Gem::Version
166
222
  version: '0'
167
223
  - !ruby/object:Gem::Dependency
168
- name: rspec-nc
224
+ name: simplecov
169
225
  requirement: !ruby/object:Gem::Requirement
170
226
  requirements:
171
227
  - - ">="
@@ -192,6 +248,20 @@ dependencies:
192
248
  - - '='
193
249
  - !ruby/object:Gem::Version
194
250
  version: 1.7.2
251
+ - !ruby/object:Gem::Dependency
252
+ name: yard
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
195
265
  description: A minimal Continuous Integration system, written in ruby, powered by
196
266
  git
197
267
  email:
@@ -222,6 +292,7 @@ files:
222
292
  - lib/tinyci/builders/script_builder.rb
223
293
  - lib/tinyci/builders/test_builder.rb
224
294
  - lib/tinyci/cli.rb
295
+ - lib/tinyci/cli_ssh_delegator.rb
225
296
  - lib/tinyci/compactor.rb
226
297
  - lib/tinyci/config.rb
227
298
  - lib/tinyci/config_transformer.rb
@@ -229,9 +300,11 @@ files:
229
300
  - lib/tinyci/git_utils.rb
230
301
  - lib/tinyci/hookers/script_hooker.rb
231
302
  - lib/tinyci/installer.rb
303
+ - lib/tinyci/log_viewer.rb
232
304
  - lib/tinyci/logging.rb
233
305
  - lib/tinyci/logo.txt
234
306
  - lib/tinyci/multi_logger.rb
307
+ - lib/tinyci/path_utils.rb
235
308
  - lib/tinyci/runner.rb
236
309
  - lib/tinyci/scheduler.rb
237
310
  - lib/tinyci/subprocesses.rb
@@ -248,7 +321,7 @@ metadata:
248
321
  allowed_push_host: https://rubygems.org
249
322
  post_install_message: " _____ _ _____ _____\n/__ (_)_ __ _ _ /
250
323
  ___/ /_ _/\n | || | '_ \\| | | |/ / / /\n | || | | | | |_| / /___/\\/ /_
251
- \ \n |_||_|_| |_|\\__, \\____/\\____/ 0.4.2\n |___/\n\n"
324
+ \ \n |_||_|_| |_|\\__, \\____/\\____/ 0.5\n |___/\n\n"
252
325
  rdoc_options: []
253
326
  require_paths:
254
327
  - lib