letitcrash 0.1.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 (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