scrutinizer-ocular 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ vendor/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scrutinizer-ocular.gemspec
4
+ gemspec
5
+
6
+ gem 'simplecov', :require => false
7
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Johannes M. Schmitt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Scrutinizer Ocular
2
+
3
+ Uploads Ruby code coverage data to [scrutinizer-ci.com](https://scrutinizer-ci.com). Internally, it relies on SimpleCov.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'scrutinizer-ocular'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install scrutinizer-ocular
19
+
20
+ ## Usage
21
+
22
+ You need to initialize ocular at the very top of your ``test_helper.rb`` or ``spec_helper.rb`` file
23
+ before loading any of your code:
24
+
25
+ ```ruby
26
+ # test_helper.rb or spec_helper.rb
27
+ require 'scrutinizer/ocular'
28
+ Scrutinizer::Ocular.watch!
29
+ ```
30
+
31
+ When running your tests on a private repository, make sure you have your access token set:
32
+
33
+ ```bash
34
+ SCRUTINIZER_ACCESS_TOKEN=abc123 bundle exec rspec spec
35
+ ```
36
+
37
+ When you [create an access token](https://scrutinizer-ci.com/profile/applications), make sure to select ``READ`` permission only.
38
+ Generally, it's a good idea to set-up a dedicated user for reporting code coverage only.
39
+
40
+ ## Advanced Use-Cases
41
+
42
+ ### Defining the SimpleCov Profile
43
+ If you would like to have SimpleCov use a specific profile, you can pass it to the watch method:
44
+
45
+ ```ruby
46
+ Scrutinizer::Ocular.watch! 'rails'
47
+ ```
48
+
49
+ ### Adding additional Formatters
50
+ If you would like to run other formatters apart from Scrutinizer's Formatter, you can add these easily:
51
+
52
+ ```ruby
53
+ require 'simplecov'
54
+ require 'scrutinizer/ocular'
55
+
56
+ # To avoid uploading coverage when running tests locally, you can use
57
+ # Scrutinizer::Ocular.should_run? and add the formatter conditionally.
58
+
59
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
60
+ SimpleCov::Formatter::HTMLFormatter,
61
+ Scrutinizer::Ocular::UploadFormatter
62
+ ]
63
+ SimpleCov.start
64
+ ```
65
+
66
+ ### Merging Coverage from Parallelized Runs
67
+ Scrutinizer supports merging coverage from parallelized runs natively. All you need to do is
68
+ [adjust your .scrutinizer.yml](https://scrutinizer-ci.com/docs/tools/external-code-coverage/)
69
+ to tell us how many submissions you are going to make; no further changes are necessary.
70
+
71
+ ## Credits
72
+ Parts of this code were inspired by the Ruby Coveralls implementation and were received under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run RSpec"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.verbose = false
10
+ end
11
+
12
+ task :default => :spec
@@ -0,0 +1,48 @@
1
+ require "scrutinizer/ocular/version"
2
+
3
+ module Scrutinizer
4
+ module Ocular
5
+ extend self
6
+
7
+ attr_accessor :enabled
8
+ @enabled = false
9
+
10
+ def watch!(profile = nil, &block)
11
+ if self.should_run?
12
+ setup!
13
+ start! profile, &block
14
+ end
15
+ end
16
+
17
+ def setup!
18
+ require 'simplecov'
19
+ require "scrutinizer/ocular/formatter"
20
+
21
+ ::SimpleCov.formatter = create_formatter
22
+ end
23
+
24
+ def create_formatter
25
+ if ENV['SCRUTINIZER_CC_FILE']
26
+ return Scrutinizer::Ocular::LocalOutputFormatter
27
+ end
28
+
29
+ Scrutinizer::Ocular::UploadingFormatter
30
+ end
31
+
32
+ def start!(profile=nil, &block)
33
+ ::SimpleCov.add_filter 'vendor'
34
+
35
+ if profile
36
+ ::SimpleCov.start(profile)
37
+ elsif block
38
+ ::SimpleCov.start(profile) { instance_eval(block) }
39
+ else
40
+ ::SimpleCov.start
41
+ end
42
+ end
43
+
44
+ def should_run?
45
+ ENV["CI"] || ENV["JENKINS_URL"] || ENV["SCRUTINIZER_COVERAGE"] || ENV['SCRUTINIZER_CC_FILE'] || @enabled
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'json'
4
+ require 'base64'
5
+
6
+ module Scrutinizer
7
+ module Ocular
8
+ class UploadFailed < StandardError
9
+ attr_reader :response
10
+
11
+ def initialize(response)
12
+ @response = response
13
+ end
14
+ end
15
+
16
+ class ApiClient
17
+ def initialize(api_url, repository_name, revision, parents, access_token)
18
+ @api_url = api_url
19
+ @repository_name = repository_name
20
+ @revision = revision
21
+ @parents = parents
22
+ @access_token = access_token
23
+ @http = create_http_service(@api_url)
24
+
25
+ disable_net_blockers!
26
+ end
27
+
28
+ def upload(format, data)
29
+ uri = URI.parse(@api_url + '/repositories/' + @repository_name + '/data/code-coverage')
30
+
31
+ request = Net::HTTP::Post.new(uri.request_uri)
32
+ request.add_field('Content-Type', 'application/json')
33
+ request.body = {
34
+ :revision => @revision,
35
+ :parents => @parents,
36
+ :coverage => {
37
+ :format => format,
38
+ :data => Base64.encode64(data)
39
+ }
40
+ }.to_json
41
+
42
+ response = @http.request(request)
43
+
44
+ if response.code.to_i < 200 || response.code.to_i >= 300
45
+ raise UploadFailed.new(response), "Upload failed with status #{response.code}"
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def create_http_service(url)
52
+ uri = URI(url)
53
+ pem_file = File.absolute_path(File.dirname(__FILE__) + '/../../../res/cacert.pem')
54
+ pem = File.read(pem_file)
55
+ http = Net::HTTP.new(uri.host, uri.port)
56
+ http.use_ssl = true
57
+ http.cert = OpenSSL::X509::Certificate.new(pem)
58
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
59
+
60
+ http
61
+ end
62
+
63
+ def disable_net_blockers!
64
+ host = URI(@api_url).host
65
+
66
+ if defined?(WebMock)
67
+ allow = WebMock::Config.instance.allow || []
68
+ WebMock::Config.instance.allow = [*allow].push host
69
+ end
70
+
71
+ if defined?(VCR)
72
+ VCR.send(VCR.version.major < 2 ? :config : :configure) do |c|
73
+ c.ignore_hosts host
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,76 @@
1
+ require 'scrutinizer/ocular/api_client'
2
+ require 'scrutinizer/ocular/repository_introspector'
3
+ require 'scrutinizer/ocular/output'
4
+ require 'json'
5
+
6
+ module Scrutinizer
7
+ module Ocular
8
+ class Serializer
9
+ def serialize(result)
10
+ files = []
11
+ result.files.each do |file|
12
+ files << {
13
+ :name => file.filename,
14
+ :lines => file.coverage
15
+ }
16
+ end
17
+
18
+ files.to_json
19
+ end
20
+ end
21
+
22
+ class LocalOutputFormatter
23
+ def initialize
24
+ @output_file = ENV['SCRUTINIZER_CC_FILE'] || "./coverage.json"
25
+ @serializer = Serializer.new
26
+ end
27
+
28
+ def format(result)
29
+ File.open(@output_file, 'w') do |file|
30
+ file.write(@serializer.serialize(result))
31
+ end
32
+ end
33
+ end
34
+
35
+ class UploadingFormatter
36
+ attr_accessor :output
37
+
38
+ def initialize
39
+ introspector = RepositoryIntrospector.new(Dir.pwd)
40
+
41
+ api_url = ENV['SCRUTINIZER_HOST'] || "https://scrutinizer-ci.com/api"
42
+ access_token = ENV['SCRUTINIZER_ACCESS_TOKEN'] || nil
43
+ @repository = ENV['SCRUTINIZER_REPOSITORY'] || introspector.get_repository_name
44
+ @revision = ENV['SCRUTINIZER_REVISION'] || introspector.get_current_revision
45
+
46
+ @api_client = ApiClient.new(
47
+ api_url,
48
+ @repository,
49
+ @revision,
50
+ introspector.get_current_parents,
51
+ access_token
52
+ )
53
+
54
+ @serializer = Serializer.new
55
+ @output = StdoutOutput.new
56
+ end
57
+
58
+ def format(result)
59
+ begin
60
+ @output.write("Uploading code coverage for '#{@repository}' and revision '#{@revision}'... ")
61
+ @api_client.upload("rb-cc", @serializer.serialize(result))
62
+ @output.write("Done!\n")
63
+ rescue UploadFailed => e
64
+ @output.write("Failed\n")
65
+
66
+ if e.response.code.to_i == 401 || e.response.code.to_i == 403
67
+ @output.write("Please make sure to set an access token via the environment variable 'SCRUTINIZER_ACCESS_TOKEN'\n")
68
+ @output.write("You can obtain access tokens with 'READ' permission on https://scrutinizer-ci.com/profile/applications")
69
+ else
70
+ @output.write(e.response.body)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ module Scrutinizer
2
+ module Ocular
3
+ class NullOutput
4
+ def write(message)
5
+ # Do nothing
6
+ end
7
+ end
8
+
9
+ class MemorizedOutput
10
+ attr_reader :output
11
+
12
+ def initialize
13
+ @output = ''
14
+ end
15
+
16
+ def write(message)
17
+ @output += message
18
+ end
19
+ end
20
+
21
+ class StdoutOutput
22
+ def write(message)
23
+ print(message)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ require 'open3'
2
+
3
+ module Scrutinizer
4
+ module Ocular
5
+ class RepositoryIntrospector
6
+ def initialize(dir)
7
+ @dir = dir
8
+ end
9
+
10
+ def get_repository_name()
11
+ stdout, status = Open3.capture2('git remote -v', :chdir => @dir)
12
+
13
+ raise 'Repository name could not be determined' unless status.exitstatus == 0
14
+
15
+ output = stdout.to_s
16
+ patterns = [
17
+ /^origin\s+(?:git@|(?:git|https?):\/\/)([^:\/]+)(?:\/|:)([^\/]+)\/([^\/\s]+?)(?:\.git)?(?:\s|\n)/,
18
+ /^[^\s]+\s+(?:git@|(?:git|https?):\/\/)([^:\/]+)(?:\/|:)([^\/]+)\/([^\/\s]+?)(?:\.git)?(?:\s|\n)/,
19
+ ]
20
+
21
+ patterns.each { |pattern|
22
+ if output =~ pattern
23
+ return get_repository_type($1) + '/' + $2 + '/' + $3
24
+ end
25
+ }
26
+
27
+ raise "Could not determine repository. Please set the 'SCRUTINIZER_REPOSITORY' environment variable"
28
+ end
29
+
30
+ def get_current_parents
31
+ stdout, status = Open3.capture2('git log --pretty=%P -n1 HEAD', :chdir => @dir)
32
+
33
+ raise 'Parents could not be determined' unless status.exitstatus == 0
34
+
35
+ output = stdout.to_s.strip
36
+ if output.empty?
37
+ return []
38
+ end
39
+
40
+ output.split(' ')
41
+ end
42
+
43
+ def get_current_revision
44
+ stdout, status = Open3.capture2('git rev-parse HEAD', :chdir => @dir)
45
+
46
+ raise 'Revision could not be determined' unless status.exitstatus == 0
47
+
48
+ stdout.to_s.strip
49
+ end
50
+
51
+ private
52
+ def get_repository_type(host)
53
+ if host == "github.com"
54
+ return "g"
55
+ elsif host == "bitbucket.org"
56
+ return "b"
57
+ end
58
+
59
+ raise "Unknown host " + host
60
+ end
61
+ end
62
+ end
63
+ end
64
+