october 0.1.1

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. data/Gemfile +35 -0
  2. data/README.md +14 -0
  3. data/Rakefile +38 -0
  4. data/bin/october +8 -0
  5. data/boot.rb +5 -0
  6. data/config/database.yml.example +3 -0
  7. data/config/irc.yml.example +9 -0
  8. data/config/plugins.yml.example +2 -0
  9. data/config/redis.yml.example +7 -0
  10. data/lib/october.rb +16 -0
  11. data/lib/october/base.rb +28 -0
  12. data/lib/october/config.rb +23 -0
  13. data/lib/october/debugger.rb +51 -0
  14. data/lib/october/environment.rb +24 -0
  15. data/lib/october/plugin.rb +26 -0
  16. data/lib/october/plugin/help.rb +20 -0
  17. data/lib/october/plugins.rb +94 -0
  18. data/lib/october/redis.rb +20 -0
  19. data/lib/october/version.rb +3 -0
  20. data/lib/october/watchdog.rb +36 -0
  21. data/october.gemspec +25 -0
  22. data/plugins/.gitkeep +0 -0
  23. data/plugins/autossh.rb +23 -0
  24. data/plugins/fortune.rb +13 -0
  25. data/plugins/help.rb +11 -0
  26. data/plugins/hudson.rb +73 -0
  27. data/plugins/hudson/config.rb +41 -0
  28. data/plugins/hudson/fetcher.rb +40 -0
  29. data/plugins/hudson/reporter.rb +104 -0
  30. data/plugins/hudson/test_run.rb +88 -0
  31. data/plugins/issues.rb +161 -0
  32. data/plugins/joke.rb +32 -0
  33. data/plugins/links.rb +42 -0
  34. data/plugins/links/link.rb +68 -0
  35. data/plugins/service.rb +11 -0
  36. data/plugins/update.rb +79 -0
  37. data/plugins/whisper.rb +70 -0
  38. data/spec/fixtures/hudson/console.log +37 -0
  39. data/spec/helpers/bot_context.rb +5 -0
  40. data/spec/october/environment_spec.rb +28 -0
  41. data/spec/plugins/hudson/reporter_spec.rb +27 -0
  42. data/spec/plugins/hudson/test_run_spec.rb +17 -0
  43. data/spec/plugins/hudson_spec.rb +44 -0
  44. data/spec/plugins/issues_spec.rb +26 -0
  45. data/spec/plugins/links_spec.rb +16 -0
  46. data/spec/plugins/whisper_spec.rb +6 -0
  47. data/spec/spec_helper.rb +13 -0
  48. metadata +185 -0
@@ -0,0 +1,3 @@
1
+ module October
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,36 @@
1
+ module October
2
+ class Watchdog
3
+ attr_reader :child, :root, :env
4
+
5
+ def initialize(root, env = nil)
6
+ @root = root
7
+ @env = env
8
+ end
9
+
10
+ def spawn!
11
+ @child = fork do
12
+ ENV['OCTOBER_ENV'] ||= env
13
+
14
+ require File.join(root, "boot")
15
+ require 'october'
16
+
17
+ October::Base.start
18
+ end
19
+ end
20
+
21
+ def loop!
22
+ loop do
23
+ begin
24
+ Process.wait(spawn!)
25
+ warn "process #{child} ended"
26
+ rescue Errno::ECHILD
27
+ warn "process #{child} died or was killed"
28
+ end
29
+
30
+ warn "spawning new one in next iteration"
31
+
32
+ sleep(1) # throttle spawning in case anything fails
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../lib/october/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Michal Cichra"]
5
+ gem.email = ["mikz@o2h.cz"]
6
+ gem.description = %q{IRC Bot}
7
+ gem.summary = gem.description
8
+ gem.homepage = "https://github.com/mikz/october"
9
+
10
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
+ gem.files = `git ls-files`.split("\n") - %w{.gitignore .rvmrc .travis.yml Guardfile Gemfile.lock}
12
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ gem.name = "october"
14
+ gem.require_paths = ["lib", "plugins"]
15
+ gem.version = October::VERSION
16
+
17
+ gem.add_dependency 'activesupport'
18
+ gem.add_dependency 'i18n'
19
+
20
+ gem.add_dependency 'cinch'
21
+
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'fakefs'
24
+ end
25
+
File without changes
@@ -0,0 +1,23 @@
1
+ class Autossh
2
+ include October::Plugin
3
+
4
+ match /tunnels?$/, method: :reconnect
5
+
6
+ register_help 'tunnel[s]', 'Reconnects autossh tunnels'
7
+
8
+ def reconnect(m)
9
+ pids = IO.popen('ps ux | grep "auto[s]sh"') do |io|
10
+ io.readlines.map { |line|
11
+ line.split(/\s+/)[1].to_i
12
+ }
13
+ end
14
+
15
+ m.reply "Found #{pids.count} autossh processes"
16
+
17
+ unless pids.empty?
18
+ m.reply "Sending SIGUSR1 to pid: #{pids.join(", ")}"
19
+ Process.kill('USR1', *pids)
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,13 @@
1
+ gem 'typhoeus'
2
+
3
+ class Fortune
4
+ include October::Plugin
5
+
6
+ match /fortune?$/, method: :fortune
7
+
8
+ register_help 'fortune', 'fortune YO!'
9
+ def fortune(m)
10
+ response = Typhoeus::Request.get "http://www.fortunefortoday.com/getfortuneonly.php"
11
+ m.reply response.body.strip
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class Help
2
+ include October::Plugin
3
+ register_help 'help', 'list all registered commands with description'
4
+
5
+ match 'help', method: :help
6
+
7
+ def help(m)
8
+ m.user.msg 'October help:'
9
+ m.user.msg self.class.list_help
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ require 'typhoeus'
2
+
3
+ class Hudson
4
+ include October::Plugin
5
+
6
+ autoload :Fetcher, 'hudson/fetcher'
7
+ autoload :Reporter, 'hudson/reporter'
8
+ autoload :TestRun, 'hudson/test_run'
9
+ autoload :Config, 'hudson/config'
10
+
11
+ HYDRA = Typhoeus::Hydra.new
12
+
13
+ FAILED = /(?:failures|failed|f)/
14
+ NUMBER = /(?:\/(\d+))?/
15
+ JOB = /([\w\-\.]+?)/
16
+ BUILD = /#{JOB}#{NUMBER}/
17
+
18
+ match /#{FAILED}(?:\s+#{BUILD})?$/, method: :failures
19
+ match /(?:failures|failed|f) #{BUILD} diff #{BUILD}$/, method: :diff
20
+ match /Project (.+?) build #(\d+): (?:SUCCESS|FIXED) (?:.+?): (.*)$/, method: :green, :use_prefix => false
21
+ match /(?:job|j) (\S*)(?: ?)(.*?)$/, method: :update_branch
22
+ match /(?:build|b)(?: ?)(.*?)$/, method: :build
23
+
24
+ register_help 'failures|failed|f project', 'list failed tests (cukes and test units) from last test run'
25
+ register_help 'failures|failed|f project/test_number', 'list failed tests from specific test run'
26
+ register_help 'failures|failed|f project/test_number diff another/test', 'list only difference between these two tests'
27
+ register_help 'build [job_name]', 'builds your personal project or the given job'
28
+ register_help 'job branch_name [job_name]', 'updates your personal project or the specified job to build the given branch'
29
+
30
+ def failures(m, job = nil, test_run = nil)
31
+ job ||= m.user
32
+ test = TestRun.new job, test_run
33
+ reporter = Reporter.new test
34
+
35
+ reporter.respond :report, m
36
+ end
37
+
38
+ def diff(m, *projects)
39
+ tests = projects.in_groups_of(2).map {|project, number|
40
+ TestRun.new project, number
41
+ }
42
+ reporter = Reporter.new *tests
43
+
44
+ reporter.respond :diff, m
45
+ end
46
+
47
+ def green(m, project_name, build, url)
48
+ if October::Plugins.registered["Hudson"]
49
+ tr = TestRun.new(project_name, build)
50
+ issues = @bot.plugins.detect{|a| a.is_a? Issues}
51
+ pull = issues.pull_request(m, tr.branch)
52
+ issues.comment(m, pull["number"], "Green: #{url}") if pull
53
+ end
54
+ end
55
+
56
+ def update_branch(m, new_branch, job_name)
57
+ job_name = m.user.to_s if job_name.blank?
58
+ config = Config.new(job_name)
59
+ config.update_branch(new_branch)
60
+ m.reply "Job '#{job_name}' set to build branch '#{new_branch}'"
61
+ rescue
62
+ m.reply "Error updating job"
63
+ end
64
+
65
+ def build(m, job_name)
66
+ job_name = m.user.to_s if job_name.blank?
67
+ config = Config.new(job_name)
68
+ config.build
69
+ m.reply "Build of '#{job_name}' scheduled"
70
+ rescue
71
+ m.reply "Failed to schedule a build for '#{job_name}'"
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ require 'open-uri'
2
+
3
+ class Hudson
4
+ class Config
5
+ BASE_URL = "http://localhost:8080"
6
+ BUILD_URL = "/job/<project>/build"
7
+ CONFIG_URL = "/job/<project>/config.xml"
8
+
9
+ attr_reader :job
10
+
11
+ def initialize(job)
12
+ @job = job
13
+ end
14
+
15
+ def config_url
16
+ (BASE_URL + CONFIG_URL).gsub('<project>', job)
17
+ end
18
+
19
+ def build_url
20
+ (BASE_URL + BUILD_URL).gsub('<project>', job)
21
+ end
22
+
23
+ def config
24
+ @config ||= Nokogiri::XML(open(config_url))
25
+ end
26
+
27
+ def build
28
+ open(build_url)
29
+ end
30
+
31
+ def update_branch(branch_name)
32
+ config.xpath('/project/scm/branches/hudson.plugins.git.BranchSpec/name').each do |branch|
33
+ branch.content = branch_name
34
+ end
35
+
36
+ Curl::Easy.http_post(config_url, config.to_s) do |curl|
37
+ curl.headers['Content-Type'] = 'text/xml'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ class Hudson
2
+ class Fetcher < Typhoeus::Request
3
+ BASE_URL = "http://localhost:8080"
4
+ JOB_URL = "/job/<project>/<test_run>/consoleText"
5
+
6
+ attr_reader :test_run
7
+
8
+ def initialize test_run, options = {}
9
+ @test_run = test_run
10
+
11
+ url = (BASE_URL + JOB_URL).
12
+ gsub('<project>', test_run.project.to_s).
13
+ gsub('<test_run>', test_run.number.to_s)
14
+
15
+ super(url, options)
16
+
17
+ self.authorize = {
18
+ :username => ENV['HUDSON_USER'].presence,
19
+ :password => ENV['HUDSON_PASS'].presence
20
+ }
21
+
22
+ end
23
+
24
+ def authorize= options
25
+ @username = options[:username]
26
+ @password = options[:password]
27
+ end
28
+
29
+ def response
30
+ @response or run
31
+ end
32
+
33
+ def run
34
+ HYDRA.queue self
35
+ HYDRA.run
36
+ self.response
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,104 @@
1
+ class Hudson
2
+ class Reporter
3
+
4
+ class Diff
5
+ include Enumerable
6
+
7
+ def initialize(base, other)
8
+ @base, @other = base, other
9
+ end
10
+
11
+ def header
12
+ "--- #{@base.project}/#{@base.number} <=> #{@other.project}/#{@other.number} ---"
13
+ end
14
+
15
+ def each(&block)
16
+ failures.each(&block)
17
+ end
18
+
19
+ def to_s
20
+ [header, *surplus, *common, *missing].join("\n")
21
+ end
22
+
23
+ delegate :length, :to => :all
24
+
25
+ private
26
+ def surplus
27
+ prefix('+', @other.failures - @base.failures)
28
+ end
29
+
30
+ def missing
31
+ prefix('-', @base.failures - @other.failures)
32
+ end
33
+
34
+ def all
35
+ @failures ||= @base.failures | @other.failures
36
+ end
37
+
38
+ def common
39
+ prefix(' ', @base.failures & @other.failures)
40
+ end
41
+
42
+ def prefix(str, array)
43
+ array.map{ |line| str + " " << line }
44
+ end
45
+
46
+ end
47
+
48
+ class Report < Array
49
+ def initialize test
50
+ @project, @number = test.project, test.number
51
+ super(test.failures.flatten)
52
+ end
53
+
54
+ def header
55
+ "------- #{@project}/#{@number} -------"
56
+ end
57
+
58
+ def to_s
59
+ [header, *self].join("\n")
60
+ end
61
+ end
62
+
63
+ attr_reader :tests
64
+
65
+ def initialize *tests
66
+ @tests = tests.flatten
67
+ end
68
+
69
+ def report
70
+ Report.new(@tests.first)
71
+ end
72
+
73
+ def diff
74
+ Diff.new(@tests.first, @tests.last)
75
+ end
76
+
77
+ def respond format, message
78
+
79
+ if responses.all? &:success?
80
+ return message.user.msg self.send(format)
81
+ end
82
+
83
+ responses(&:timed_out?).each do |r|
84
+ message.reply 'Some requests timed out :('
85
+ end
86
+
87
+ responses{|r| r.code == 0 }.each do |r|
88
+ message.reply r.curl_error_message
89
+ end
90
+
91
+ responses{|r| r.code != 200 }.each do |r|
92
+ message.reply "HTTP request failed: #{r.code}"
93
+ end
94
+
95
+ end
96
+
97
+ private
98
+
99
+ def responses(&block)
100
+ @tests.map(&:response).select &block
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,88 @@
1
+ class Hudson
2
+ class TestRun
3
+
4
+ class Cucumber
5
+ PATTERN = /^cucumber (?:.+?\/)(features\/.+?\.feature:\d+)/
6
+
7
+ def self.parse(log)
8
+ log.scan(PATTERN).flatten.uniq.map{ |name| self.new(name) }
9
+ end
10
+
11
+ def initialize(file)
12
+ @file = file
13
+ end
14
+
15
+ def to_s
16
+ @file
17
+ end
18
+ alias :to_str :to_s
19
+
20
+ def ==(other)
21
+ to_s == other.try(:to_s)
22
+ end
23
+ end
24
+
25
+ class TestUnit
26
+ PATTERN = /\d+\) (?:Failure|Error):\n(.+?)(?:\n{2})/m
27
+ NAME = /(.+)\((.+)\)[\n: ]+/
28
+ FILE = /(?:(test\/.+.[a-z]+):\d+)+(?::in `.+')?/
29
+
30
+ def self.parse(log)
31
+ blocks = log.scan(PATTERN).flatten
32
+ blocks.map do |block|
33
+ name = block.scan(NAME).first.first
34
+ file = block.scan(FILE).last.try(:join) || '(missing file)'
35
+ self.new(name, file)
36
+ end
37
+ end
38
+
39
+ def initialize(name, file)
40
+ @name, @file = name, file
41
+ end
42
+
43
+ def to_s
44
+ "#{@file} -n '#{@name}'"
45
+ end
46
+ alias :to_str :to_s
47
+
48
+ def ==(other)
49
+ to_s == other.try(:to_s)
50
+ end
51
+ end
52
+
53
+
54
+ attr_reader :project, :number
55
+
56
+ def initialize(project, number = nil)
57
+ @project = project
58
+ @number = number.presence || 'lastBuild'
59
+
60
+ @fetcher = Fetcher.new self
61
+ end
62
+
63
+ def cucumbers
64
+ @cucumbers ||= Cucumber.parse(log)
65
+ end
66
+
67
+ def test_unit
68
+ @test_unit ||= TestUnit.parse(log)
69
+ end
70
+
71
+ def branch
72
+ log.scan(/Commencing build of Revision (?:.+?) \(origin\/(.+?)\)(.*)/).first.first
73
+ end
74
+
75
+ def all
76
+ cucumbers + test_unit
77
+ end
78
+
79
+ alias :failures :all
80
+
81
+ delegate :response, :to => :@fetcher
82
+
83
+ def log
84
+ response.body
85
+ end
86
+
87
+ end
88
+ end