letitcrash 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/letitcrash/builders/file_builder.rb +75 -0
  3. data/lib/letitcrash/builders/report_builder.rb +52 -0
  4. data/lib/letitcrash/formatter.rb +29 -0
  5. data/lib/letitcrash/matcher.rb +46 -0
  6. data/lib/letitcrash/matchers/app_veyor.rb +19 -0
  7. data/lib/letitcrash/matchers/base.rb +31 -0
  8. data/lib/letitcrash/matchers/buildkite.rb +19 -0
  9. data/lib/letitcrash/matchers/circle_ci.rb +19 -0
  10. data/lib/letitcrash/matchers/codefresh.rb +20 -0
  11. data/lib/letitcrash/matchers/codeship.rb +19 -0
  12. data/lib/letitcrash/matchers/drone.rb +19 -0
  13. data/lib/letitcrash/matchers/git.rb +27 -0
  14. data/lib/letitcrash/matchers/gitlab_ci.rb +20 -0
  15. data/lib/letitcrash/matchers/jenkins.rb +20 -0
  16. data/lib/letitcrash/matchers/semaphore.rb +19 -0
  17. data/lib/letitcrash/matchers/shippable.rb +19 -0
  18. data/lib/letitcrash/matchers/snap_ci.rb +19 -0
  19. data/lib/letitcrash/matchers/solano_ci.rb +19 -0
  20. data/lib/letitcrash/matchers/team_city.rb +19 -0
  21. data/lib/letitcrash/matchers/travis_ci.rb +19 -0
  22. data/lib/letitcrash/matchers/wercker.rb +20 -0
  23. data/lib/letitcrash/proto/structs.rb +34 -0
  24. data/lib/letitcrash/reporters/stream.rb +19 -0
  25. data/lib/letitcrash/reporters/upload.rb +40 -0
  26. data/lib/letitcrash/sanitizer.rb +37 -0
  27. data/lib/letitcrash/version.rb +5 -0
  28. data/lib/letitcrash.rb +36 -0
  29. data/spec/builders/file_builder_spec.rb +68 -0
  30. data/spec/builders/report_builder_spec.rb +62 -0
  31. data/spec/formatter_spec.rb +57 -0
  32. data/spec/matcher_spec.rb +31 -0
  33. data/spec/matchers/app_veyor_spec.rb +44 -0
  34. data/spec/matchers/base_spec.rb +15 -0
  35. data/spec/matchers/buildkite_spec.rb +44 -0
  36. data/spec/matchers/circle_ci_spec.rb +44 -0
  37. data/spec/matchers/codefresh_spec.rb +34 -0
  38. data/spec/matchers/codeship_spec.rb +44 -0
  39. data/spec/matchers/drone_spec.rb +44 -0
  40. data/spec/matchers/git_spec.rb +60 -0
  41. data/spec/matchers/gitlab_ci_spec.rb +36 -0
  42. data/spec/matchers/jenkins_ci_spec.rb +36 -0
  43. data/spec/matchers/semaphore_spec.rb +44 -0
  44. data/spec/matchers/shared_helpers.rb +17 -0
  45. data/spec/matchers/shippable_spec.rb +44 -0
  46. data/spec/matchers/snap_ci_spec.rb +44 -0
  47. data/spec/matchers/solano_ci_spec.rb +36 -0
  48. data/spec/matchers/team_city_spec.rb +36 -0
  49. data/spec/matchers/travis_ci_spec.rb +44 -0
  50. data/spec/matchers/wercker_spec.rb +42 -0
  51. data/spec/reporters/stream_spec.rb +26 -0
  52. data/spec/reporters/upload_spec.rb +45 -0
  53. data/spec/sanitizer_spec.rb +23 -0
  54. data/spec/spec_helper.rb +19 -0
  55. metadata +374 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9730e1009fb2c24101ec788ca6c2b7b1018d6947
4
+ data.tar.gz: 795be7e067ad540b7f1c8c937956a9ad76e429a3
5
+ SHA512:
6
+ metadata.gz: 0f62201242d45e2983322d87bb96f930cdd085cf56610f3697a0550fa10f7ddf313b36d91bc7fd769fe6ed8b42da0547f86ff3a45803f15398fe114a0953b731
7
+ data.tar.gz: 3d9530be2bbcee55e9fad0e7dfccafc602c1f864c898abe217a29cacdc77c2a65eb2be88e829d30a2125627ee35c5da45b0729ce4571ed8f766e7142d1d05118
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'simplecov'
5
+
6
+ module LetItCrash
7
+ module Builders
8
+ class FileBuilder
9
+ extend Forwardable
10
+
11
+ def_delegators :file, :filename, :lines
12
+
13
+ def self.build(file:, rewriter:)
14
+ new(file: file, rewriter: rewriter).build
15
+ end
16
+
17
+ def initialize(file:, rewriter:)
18
+ @file = file
19
+ @rewriter = rewriter
20
+ end
21
+
22
+ def build
23
+ Proto::File.new(
24
+ path: path,
25
+ digest: digest,
26
+ lines: relevant_lines,
27
+ coverage: coverage,
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :file, :rewriter
34
+
35
+ def path
36
+ Proto::File::Path.new(
37
+ relative: relative,
38
+ rewritten: rewritten,
39
+ )
40
+ end
41
+
42
+ def relevant_lines
43
+ @relevant_lines ||= lines.each_with_object({}) do |line, ret|
44
+ number = line.line_number
45
+ relevant = sanitizer.include?(number) && !line.skipped?
46
+ ret[number] = line.coverage.to_i if relevant
47
+ end
48
+ end
49
+
50
+ def coverage
51
+ relevant_lines.values.count(&:positive?) / relevant_lines.count.to_f
52
+ end
53
+
54
+ def relative
55
+ pathname = Pathname.new(filename)
56
+ pathname.absolute? ? pathname.relative_path_from(ROOT).to_s : filename
57
+ end
58
+
59
+ def rewritten
60
+ rewriter ? rewriter.call(relative) : ''
61
+ end
62
+
63
+ def digest
64
+ Digest::SHA256.file(filename).hexdigest
65
+ end
66
+
67
+ def sanitizer
68
+ @sanitizer ||= Sanitizer.from_path(path: filename)
69
+ end
70
+
71
+ ROOT = Pathname.new(SimpleCov.root)
72
+ private_constant :ROOT
73
+ end # class FileBuilder
74
+ end # module Builders
75
+ end # module LetItCrash
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module LetItCrash
6
+ module Builders
7
+ class ReportBuilder
8
+ extend Forwardable
9
+
10
+ def_delegators :matcher, :matches?, :branch, :sha
11
+
12
+ # @param result: result [SimpleCov::Result]
13
+ # @param rewriter: rewriter [Proc]
14
+ #
15
+ # @return [LetItCrash::Proto::Report]
16
+ def self.build(environment:, result:, rewriter:)
17
+ new(environment: environment, result: result, rewriter: rewriter).build
18
+ end
19
+
20
+ def initialize(environment:, result:, rewriter:)
21
+ @environment = environment
22
+ @result = result
23
+ @rewriter = rewriter
24
+ end
25
+
26
+ def build
27
+ Proto::Report.new(commit: commit, files: files)
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :environment, :result, :rewriter
33
+
34
+ def commit
35
+ raise MatcherMissing unless matches?
36
+ Proto::Report::Commit.new(branch: branch, sha: sha)
37
+ end
38
+
39
+ def files
40
+ result.files.map do |file|
41
+ FileBuilder.build(file: file, rewriter: rewriter)
42
+ end
43
+ end
44
+
45
+ def matcher
46
+ @matcher ||= Matcher.new(environment: environment)
47
+ end
48
+
49
+ class MatcherMissing < RuntimeError; end
50
+ end # class ReportBuilder
51
+ end # module Builders
52
+ end # module LetItCrash
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ class Formatter
5
+ def initialize(rewriter: nil, reporters: [Reporters::Upload.from_env])
6
+ @rewriter = rewriter
7
+ @reporters = reporters
8
+ end
9
+
10
+ def format(result)
11
+ report = Builders::ReportBuilder.build(
12
+ environment: ENV,
13
+ result: result,
14
+ rewriter: rewriter,
15
+ )
16
+ reporters.each { |reporter| reporter.report(report) }
17
+ end
18
+
19
+ # This unfortunate trick is required since SimpleCov expects formatters to
20
+ # be classes rather than instances, and calls `new` on them.
21
+ def new
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :rewriter, :reporters
28
+ end # class Formatter
29
+ end # module LetItCrash
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module LetItCrash
6
+ class Matcher < Matchers::Base
7
+ extend Forwardable
8
+
9
+ def_delegators :matcher, :branch, :sha
10
+
11
+ # This method performs a :reek:NilCheck.
12
+ def matches?
13
+ !matcher.nil?
14
+ end
15
+
16
+ private
17
+
18
+ def matcher
19
+ @matcher ||= MATCHERS.each do |klass|
20
+ instance = klass.new(environment: environment)
21
+ return instance if instance.matches?
22
+ end
23
+ nil
24
+ end
25
+
26
+ MATCHERS = [
27
+ Matchers::AppVeyor,
28
+ Matchers::Buildkite,
29
+ Matchers::CircleCI,
30
+ Matchers::Codefresh,
31
+ Matchers::Codeship,
32
+ Matchers::Drone,
33
+ Matchers::GitLabCI,
34
+ Matchers::Jenkins,
35
+ Matchers::Semaphore,
36
+ Matchers::Shippable,
37
+ Matchers::SnapCI,
38
+ Matchers::SolanoCI,
39
+ Matchers::TeamCity,
40
+ Matchers::TravisCI,
41
+ Matchers::Wercker,
42
+ Matchers::Git # This is a fallback.
43
+ ].freeze
44
+ private_constant :MATCHERS
45
+ end # class Matcher
46
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class AppVeyor < Base
6
+ def matches?
7
+ ci? && environment['APPVEYOR'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['APPVEYOR_REPO_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['APPVEYOR_REPO_COMMIT']
16
+ end
17
+ end # class AppVeyor
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Base
6
+ def initialize(environment:)
7
+ @environment = environment
8
+ end
9
+
10
+ def matches?
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def branch
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def sha
19
+ raise NotImplementedError
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :environment
25
+
26
+ def ci?
27
+ environment['CI'] == 'true'
28
+ end
29
+ end # class Base
30
+ end # module Matchers
31
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Buildkite < Base
6
+ def matches?
7
+ ci? && environment['BUILDKITE'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['BUILDKITE_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['BUILDKITE_COMMIT']
16
+ end
17
+ end # class Buildkite
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class CircleCI < Base
6
+ def matches?
7
+ ci? && environment['CIRCLECI'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['CIRCLE_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['CIRCLE_SHA1']
16
+ end
17
+ end # class CircleCI
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Codefresh < Base
6
+ # This method performs a :reek:NilCheck.
7
+ def matches?
8
+ !branch.nil?
9
+ end
10
+
11
+ def branch
12
+ environment['CF_BRANCH']
13
+ end
14
+
15
+ def sha
16
+ environment['CF_REVISION']
17
+ end
18
+ end # class Codefresh
19
+ end # module Matchers
20
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Codeship < Base
6
+ def matches?
7
+ ci? && environment['CI_NAME'] == 'codeship'
8
+ end
9
+
10
+ def branch
11
+ environment['CI_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['CI_COMMIT_ID']
16
+ end
17
+ end # class Codeship
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Drone < Base
6
+ def matches?
7
+ ci? && environment['DRONE'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['DRONE_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['DRONE_COMMIT']
16
+ end
17
+ end # class Drone
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Git < Base
6
+ STATUS_CMD = 'git status'
7
+ BRANCH_CMD = 'git rev-parse --abbrev-ref HEAD'
8
+ SHA_CMD = 'git rev-parse HEAD'
9
+
10
+ # This method reeks of :reek:UtilityFunction.
11
+ def matches?
12
+ IO.popen(STATUS_CMD).read.start_with?('On branch')
13
+ end
14
+
15
+ # This method reeks of :reek:UtilityFunction.
16
+ def branch
17
+ ret = IO.popen(BRANCH_CMD).read.strip
18
+ ret == 'HEAD' ? 'master' : ret
19
+ end
20
+
21
+ # This method reeks of :reek:UtilityFunction.
22
+ def sha
23
+ IO.popen(SHA_CMD).read.strip
24
+ end
25
+ end # class Git
26
+ end # module Matchers
27
+ end # module LetItCrash
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class GitLabCI < Base
6
+ # This method performs a :reek:NilCheck.
7
+ def matches?
8
+ !environment['GITLAB_CI'].nil?
9
+ end
10
+
11
+ def branch
12
+ environment['CI_BUILD_REF_NAME'] || environment['CI_COMMIT_REF_NAME']
13
+ end
14
+
15
+ def sha
16
+ environment['CI_BUILD_REF'] || environment['CI_COMMIT_SHA']
17
+ end
18
+ end # class GitLabCI
19
+ end # module Matchers
20
+ end # module LetItCrash
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Jenkins < Base
6
+ # This method performs a :reek:NilCheck.
7
+ def matches?
8
+ !environment['JENKINS_URL'].nil?
9
+ end
10
+
11
+ def branch
12
+ environment['ghprbSourceBranch'] || environment['GIT_BRANCH']
13
+ end
14
+
15
+ def sha
16
+ environment['ghprbActualCommit'] || environment['GIT_COMMIT']
17
+ end
18
+ end # class Jenkins
19
+ end # module Matchers
20
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Semaphore < Base
6
+ def matches?
7
+ ci? && environment['SEMAPHORE'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['BRANCH_NAME']
12
+ end
13
+
14
+ def sha
15
+ environment['REVISION']
16
+ end
17
+ end # class Semaphore
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Shippable < Base
6
+ def matches?
7
+ ci? && environment['SHIPPABLE'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['COMMIT']
16
+ end
17
+ end # class Shippable
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class SnapCI < Base
6
+ def matches?
7
+ ci? && environment['SNAP_CI'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['SNAP_BRANCH'] || environment['SNAP_UPSTREAM_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['SNAP_COMMIT'] || environment['SNAP_UPSTREAM_COMMIT']
16
+ end
17
+ end # class SnapCI
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class SolanoCI < Base
6
+ def matches?
7
+ environment['TDDIUM'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['TDDIUM_CURRENT_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['TDDIUM_CURRENT_COMMIT']
16
+ end
17
+ end # class SolanoCI
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class TeamCity < Base
6
+ def matches?
7
+ environment['CI_SERVER_NAME'] == 'TeamCity'
8
+ end
9
+
10
+ def branch
11
+ environment['TEAMCITY_BUILD_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['TEAMCITY_BUILD_COMMIT']
16
+ end
17
+ end # class TeamCity
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class TravisCI < Base
6
+ def matches?
7
+ ci? && environment['TRAVIS'] == 'true'
8
+ end
9
+
10
+ def branch
11
+ environment['TRAVIS_BRANCH']
12
+ end
13
+
14
+ def sha
15
+ environment['TRAVIS_COMMIT']
16
+ end
17
+ end # class TravisCI
18
+ end # module Matchers
19
+ end # module LetItCrash
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Matchers
5
+ class Wercker < Base
6
+ # This method performs a :reek:NilCheck.
7
+ def matches?
8
+ ci? && !branch.nil?
9
+ end
10
+
11
+ def branch
12
+ environment['WERCKER_GIT_BRANCH']
13
+ end
14
+
15
+ def sha
16
+ environment['WERCKER_GIT_COMMIT']
17
+ end
18
+ end # class Wercker
19
+ end # module Matchers
20
+ end # module LetItCrash
@@ -0,0 +1,34 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: proto/structs.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "LetItCrash.Proto.File" do
8
+ optional :path, :message, 1, "LetItCrash.Proto.File.Path"
9
+ optional :digest, :string, 2
10
+ map :lines, :int32, :int32, 3
11
+ optional :coverage, :double, 4
12
+ end
13
+ add_message "LetItCrash.Proto.File.Path" do
14
+ optional :relative, :string, 1
15
+ optional :rewritten, :string, 2
16
+ end
17
+ add_message "LetItCrash.Proto.Report" do
18
+ optional :commit, :message, 1, "LetItCrash.Proto.Report.Commit"
19
+ repeated :files, :message, 2, "LetItCrash.Proto.File"
20
+ end
21
+ add_message "LetItCrash.Proto.Report.Commit" do
22
+ optional :branch, :string, 1
23
+ optional :sha, :string, 2
24
+ end
25
+ end
26
+
27
+ module LetItCrash
28
+ module Proto
29
+ File = Google::Protobuf::DescriptorPool.generated_pool.lookup("LetItCrash.Proto.File").msgclass
30
+ File::Path = Google::Protobuf::DescriptorPool.generated_pool.lookup("LetItCrash.Proto.File.Path").msgclass
31
+ Report = Google::Protobuf::DescriptorPool.generated_pool.lookup("LetItCrash.Proto.Report").msgclass
32
+ Report::Commit = Google::Protobuf::DescriptorPool.generated_pool.lookup("LetItCrash.Proto.Report.Commit").msgclass
33
+ end # module Proto
34
+ end # module LetItCrash
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LetItCrash
4
+ module Reporters
5
+ class Stream
6
+ def initialize(output:)
7
+ @output = output
8
+ end
9
+
10
+ def report(input)
11
+ output.write(input.to_json)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :output
17
+ end # class Stream
18
+ end # module Reporters
19
+ end # module LetItCrash
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rest-client'
4
+
5
+ module LetItCrash
6
+ module Reporters
7
+ class Upload
8
+ TOKEN_HEADER = 'X-LetItCrash-Token'
9
+
10
+ def self.from_env
11
+ endpoint = ENV.fetch(
12
+ 'LETITCRASH_ENDPOINT',
13
+ 'https://integrations.letitcrash.io/simplecov',
14
+ )
15
+ new(endpoint: endpoint, token: ENV.fetch('LETITCRASH_TOKEN'))
16
+ end
17
+
18
+ def initialize(endpoint:, token:)
19
+ @endpoint = endpoint
20
+ @token = token
21
+ end
22
+
23
+ def report(input)
24
+ RestClient.post(endpoint, input.to_json, headers)
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :endpoint, :token
30
+
31
+ def headers
32
+ {
33
+ content_type: :json,
34
+ accept: :json,
35
+ TOKEN_HEADER => token
36
+ }
37
+ end
38
+ end # class Upload
39
+ end # module Reporters
40
+ end # module LetItCrash