elastic_results 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 99161c14e7dbbdf95b0b78a312fb3c2c4e1cc7de
4
+ data.tar.gz: 3db34b799da012ecc7912dbc3bdff3f4675d94a2
5
+ SHA512:
6
+ metadata.gz: b505765a9fd4d5600e2eb33b2808cc252d6b0badce330a6fe08178ff551446a7381ffa1435e5f3bf316b2b5679bb0b49818a72c00ec4905f93a7d29682c80899
7
+ data.tar.gz: 352701d01b84d4a9c6db7d79aba5f220f796f7bc2be5872f2934fb92ec59ef250f4c2cdd71507008f670d889ee4efa0484bba4930959a6a7de49fe7a7e625fb0
data/.gitignore ADDED
@@ -0,0 +1,86 @@
1
+ ### JetBrains template
2
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
3
+
4
+ *.iml
5
+
6
+ ## Directory-based project format:
7
+ .idea/
8
+ # if you remove the above rule, at least ignore the following:
9
+
10
+ # User-specific stuff:
11
+ # .idea/workspace.xml
12
+ # .idea/tasks.xml
13
+ # .idea/dictionaries
14
+
15
+ # Sensitive or high-churn files:
16
+ # .idea/dataSources.ids
17
+ # .idea/dataSources.xml
18
+ # .idea/sqlDataSources.xml
19
+ # .idea/dynamic.xml
20
+ # .idea/uiDesigner.xml
21
+
22
+ # Gradle:
23
+ # .idea/gradle.xml
24
+ # .idea/libraries
25
+
26
+ # Mongo Explorer plugin:
27
+ # .idea/mongoSettings.xml
28
+
29
+ ## File-based project format:
30
+ *.ipr
31
+ *.iws
32
+
33
+ ## Plugin-specific files:
34
+
35
+ # IntelliJ
36
+ /out/
37
+
38
+ # mpeltonen/sbt-idea plugin
39
+ .idea_modules/
40
+
41
+ # JIRA plugin
42
+ atlassian-ide-plugin.xml
43
+
44
+ # Crashlytics plugin (for Android Studio and IntelliJ)
45
+ com_crashlytics_export_strings.xml
46
+ crashlytics.properties
47
+ crashlytics-build.properties
48
+ ### Ruby template
49
+ *.gem
50
+ *.rbc
51
+ /.config
52
+ /coverage/
53
+ /InstalledFiles
54
+ /pkg/
55
+ /spec/reports/
56
+ /spec/examples.txt
57
+ /test/tmp/
58
+ /test/version_tmp/
59
+ /tmp/
60
+
61
+ ## Specific to RubyMotion:
62
+ .dat*
63
+ .repl_history
64
+ build/
65
+
66
+ ## Documentation cache and generated files:
67
+ /.yardoc/
68
+ /_yardoc/
69
+ /doc/
70
+ /rdoc/
71
+
72
+ ## Environment normalisation:
73
+ /.bundle/
74
+ /vendor/bundle
75
+ /lib/bundler/man/
76
+
77
+ # for a library or gem, you might want to ignore these files since the code is
78
+ # intended to run in multiple environments; otherwise, check them in:
79
+ # Gemfile.lock
80
+ # .ruby-version
81
+ # .ruby-gemset
82
+
83
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
84
+ .rvmrc
85
+
86
+ ru# Created by .ignore support plugin (hsz.mobi)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in elastic_results.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ elastic_results (0.1.0)
5
+ elasticsearch
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ elasticsearch (1.0.15)
11
+ elasticsearch-api (= 1.0.15)
12
+ elasticsearch-transport (= 1.0.15)
13
+ elasticsearch-api (1.0.15)
14
+ multi_json
15
+ elasticsearch-transport (1.0.15)
16
+ faraday
17
+ multi_json
18
+ faraday (0.9.1)
19
+ multipart-post (>= 1.2, < 3)
20
+ multi_json (1.11.2)
21
+ multipart-post (2.0.0)
22
+ rake (10.4.2)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.10)
29
+ elastic_results!
30
+ rake (~> 10.0)
31
+
32
+ BUNDLED WITH
33
+ 1.10.6
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 CoverMyMeds
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # ElasticResults
2
+
3
+ Store the results of your test runs in Elasticsearch via custom formatters.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'elastic_results'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install elastic_results
21
+
22
+ ## Cucumber Usage
23
+ Add this line to your features/support/env.rb file:
24
+ ```ruby
25
+ require 'elastic_results/cucumber'
26
+ ```
27
+
28
+ Tell cucumber to use elastic_results:
29
+ ```
30
+ bundle exec cucumber --format ElasticResults::Cucumber::Formatter
31
+ ```
32
+
33
+ Tell cucumber to use elastic_results but still give normal output:
34
+ ```
35
+ bundle exec cucumber --format pretty --format ElasticResults::Cucumber::Formatter -o /dev/null
36
+ ```
37
+
38
+ Make this the default by adding the following to config/cucumber.yml
39
+ ```
40
+ default: --format pretty --format ElasticResults::Cucumber::Formatter -o /dev/null
41
+ ```
42
+
43
+ ## RSpec Usage
44
+ Add this line to your spec_helper.rb file:
45
+ ```ruby
46
+ require 'elastic_results/rspec'
47
+ ```
48
+
49
+ Tell rspec to use elastic_results:
50
+ ```
51
+ bundle exec rspec -r 'elastic_results/rspec' --format ElasticResults::RSpec::Formatter
52
+ ```
53
+
54
+ Tell rspec to use elastic_results but still give normal output:
55
+ ```
56
+ bundle exec rspec -r 'elastic_results/rspec' --format ElasticResults::RSpec::Formatter --format progress
57
+ ```
58
+
59
+ Make this the default by adding the following to spec_helper.rb
60
+ ```ruby
61
+ RSpec.configure do |config|
62
+ config.add_formatter 'ElasticResults::RSpec::Formatter'
63
+ config.add_formatter 'progress'
64
+ # the rest of your rspec configuration
65
+ end
66
+ ```
67
+ Then run rspec as normal.
68
+
69
+
70
+ ## SimpleCov Usage
71
+ Add this lines to your spec_helper.rb file:
72
+ ```ruby
73
+ require 'elastic_results/simplecov'
74
+ SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, ElasticResults::SimpleCov::Formatter]
75
+ ```
76
+ Then run simplecov as normal.
77
+
78
+
79
+
80
+ ## Configuration
81
+ elastic_results exposes several configuration points that can be set via environment variables or by accssing them on the ElasticResults module:
82
+
83
+ | ENV | ElasticResults | Default | Notes |
84
+ |--------------------|-------------------|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
85
+ | ES_URL | es_url | http://localhost | The URL where elasticsearch can be reached |
86
+ | ES_INDEX_RESULT | es_index_result | test_results-YYYY-MM-DD | The index to post test results to. The default uses a prefix of test_results and a suffix of today's date |
87
+ | ES_TYPE_RESULT | es_type_result | test_result | The data type to use for test results. |
88
+ | ES_INDEX_COVERAGE | es_index_coverage | coverage-YYYY-MM-DD | The index to post coverage data to. The default uses a prefix of coverage and a suffix of today's date |
89
+ | ES_TYPE_COVERAGE | es_type_coverage | simplecov | The data type to use for coverage data. |
90
+ | ES_LOG | es_log | ENV['DEBUG'] | Log calls to elasticsearch to STDOUT? (useful for debugging elastic_results) |
91
+ | SUITE_NAME | suite_name | Dir.pwd | The name of your suite. If not given, it will be guessed using the name of the current folder when you launch your tests. |
92
+ | SUITE_TYPE | suite_type | integration | The type of suite you're running. i.e. unit, integration, regression, etc |
93
+ | GIT_COMMIT | | Output of: git rev-parse HEAD | The git revision being tested. Jenkins will set this in CI, otherwise it's pulled from git. |
94
+ | GIT_URL | | Output of: git config --get remote.origin.url | The url to the git repo being used. Jenkins will set this in CI, otherwise it's pulled from git. |
95
+ | GIT_BRANCH | | Output of: git rev-parse --abbrev-ref HEAD | The git branch being used. Jenkins will set this in CI, otherwise it's pulled from git. |
96
+ | TEAM_NAME | team_name | ??? | The name of your team. This makes it possible to slice your results easier. |
97
+ | BUILD_URL | | | The url to your Jenkins build if any. Set by Jenkins. |
98
+ | NODE_NAME | | Socket.gethostname | The node that ran the test. Set by Jenkins, or pulled from the machine. |
99
+ | BUILD_NUMBER | | MMDDYYHHMMSSUU | The jenkins build number. If not present, a fake build number will be built using the current date/time down to the millisecond. |
100
+ | JOB_NAME | | | The jenkins job name. |
101
+ | BUILD_TAG | | | The jenkins build tag. |
102
+ | TEST_ENV/RAILS_ENV | | | The enivironment the tests were run against. Will use TEST_ENV if present, otherwise will use RAILS_ENV |
103
+ | | use_unsafe_index | true if WebMock is defined, otherwise false | If true, validation of the SSL cert for the elasticsearch host will be skipped. Useful when your testing tools break things. |
104
+
105
+
106
+ ## Example config block:
107
+ ```ruby
108
+ ElasticResults.team_name = 'MyTeam - MySubTeam'
109
+ ElasticResults.suite_type = 'unit'
110
+ ElasticResults.kibana_url = 'https://kibana.mycompany.com'
111
+ ElasticResults.es_url = %w(https://es1.mycompany.net:9200 https://es1.mycompany.net:9200).sample
112
+ ```
113
+
114
+
115
+ ## Development
116
+
117
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
118
+
119
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
120
+
121
+ ## Contributing
122
+
123
+ Bug reports and pull requests are welcome on GitHub at https://github.com/covermymeds/elastic_results
124
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'elastic_results'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'elastic_results/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'elastic_results'
8
+ spec.version = ElasticResults::VERSION
9
+ spec.authors = ['Donavan Stanley']
10
+ spec.email = ['dstanley@covermymeds.com']
11
+
12
+ spec.summary = 'Formatters to shove test results into elastic search'
13
+ spec.homepage = 'https://github.com/covermymeds/elastic_results'
14
+
15
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
16
+ # delete this section to allow pushing this gem to any host.
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
19
+ else
20
+ fail 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.10'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_dependency 'elasticsearch'
32
+ spec.add_dependency 'googl'
33
+ end
@@ -0,0 +1,70 @@
1
+ require 'cucumber/formatter/gherkin_formatter_adapter'
2
+ require 'gherkin/formatter/argument'
3
+ require 'gherkin/formatter/json_formatter'
4
+ require 'elastic_results'
5
+ require 'elasticsearch'
6
+ require 'time'
7
+ require 'socket'
8
+
9
+ module ElasticResults
10
+ # Namespace for cucumber related stuff
11
+ module Cucumber
12
+ # Replacement for the stock Json formatter that outputs to elasticsearch
13
+ class JSONFormatter < ::Gherkin::Formatter::JSONFormatter
14
+ def initialize
15
+ @feature_hashes = []
16
+ @current_step_or_hook = nil
17
+ end
18
+
19
+ def eof
20
+ feature_hash = @feature_hashes.last
21
+ feature_hash['elements'].each do |scenario_hash|
22
+ record_scenario feature_hash, scenario_hash
23
+ end
24
+ end
25
+
26
+ def done
27
+ ElasticResults.write_urls
28
+ end
29
+
30
+ private
31
+
32
+ def record_scenario(feature_hash, scenario_hash)
33
+ ElasticResults.index_result result_for(feature_hash, scenario_hash)
34
+ end
35
+
36
+ def result_for(feature_hash, scenario_hash)
37
+ Result.new.tap do |res|
38
+ res.id = scenario_hash['id']
39
+ res.feature_name = feature_hash['name']
40
+ res.scenario_name = scenario_hash['name']
41
+ res.uri = feature_hash['uri']
42
+
43
+
44
+ res.tags.append(feature_hash['tags'].map { |t| t['name'] }) unless feature_hash['tags'].nil?
45
+
46
+
47
+ res.tags.append(scenario_hash['tags'].map { |t| t['name'] }) unless scenario_hash['tags'].nil?
48
+
49
+ # Cucumber stores times in nanoseconds for each step, because that's SUPER useful!
50
+ res.runtime = scenario_hash['steps'].map { |s| s['result']['duration'] }.reduce { |a, e| a + e } / 1_000_000_000.0
51
+
52
+ failing_step = scenario_hash['steps'].find { |step| !step['result'].nil? && step['result']['status'] == :failed }
53
+ unless failing_step.nil?
54
+ res.exception_msg = failing_step['result']['error_message']
55
+ res.exception_class = res.exception_msg =~ /ExpectationNotMetError/ ? 'ExpectationNotMetError' : 'RuntimeError'
56
+ res.exception_stacktrace = "#{failing_step['name']}\n#{failing_step['match']['location']}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # A format adapter that uses our special JSON formatter
63
+ class Formatter < ::Cucumber::Formatter::GherkinFormatterAdapter
64
+ def initialize(_runtime, _io, options)
65
+ options[:expand] = true
66
+ super(ElasticResults::Cucumber::JSONFormatter.new, false, options)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ require 'time'
2
+ require 'socket'
3
+
4
+ module ElasticResults
5
+ # A common data format for test results. The Cucumber and Rspec formatters populate this class.
6
+ class Result
7
+ attr_accessor :outcome, :exception_class, :exception_msg, :exception_stacktrace, :runtime
8
+ attr_accessor :id, :uri, :feature_name, :scenario_name, :build_number
9
+
10
+ def to_hash
11
+ result_hash = base_info
12
+
13
+ [:outcome, :exception_class, :exception_msg, :exception_stacktrace, :runtime,
14
+ :id, :uri, :feature_name, :scenario_name, :tags].each do |field|
15
+ result_hash[field] = send field
16
+ end
17
+ result_hash
18
+ end
19
+
20
+ def tags
21
+ @tags ||= []
22
+ end
23
+
24
+ def outcome
25
+ @outcome ||= exception_class.nil? ? 'pass' : fail_type
26
+ end
27
+
28
+ def fail_type
29
+ exception_class =~ /ExpectationNotMet/ ? :fail : :broke
30
+ end
31
+
32
+ def base_info
33
+ {
34
+ '@timestamp' => Time.now.iso8601(3),
35
+ build_number: ENV['BUILD_NUMBER'] ? ENV['BUILD_NUMBER'] : ElasticResults.build_id,
36
+ build_url: ENV['BUILD_URL'],
37
+ node_name: ENV['NODE_NAME'] ? ENV['NODE_NAME'] : Socket.gethostname,
38
+ job_name: ENV['JOB_NAME'],
39
+ build_tag: ENV['BUILD_TAG'],
40
+ environment: ENV['TEST_ENV'] || ENV['RAILS_ENV'],
41
+ git_commit: ElasticResults.git_commit,
42
+ git_url: ElasticResults.git_url,
43
+ git_branch: ElasticResults.git_branch,
44
+ suite_name: ElasticResults.suite_name,
45
+ suite_type: ElasticResults.suite_type,
46
+ team: ElasticResults.team_name
47
+ }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ RSpec::Support.require_rspec_core 'formatters/base_formatter'
2
+ require 'elastic_results'
3
+ require 'json'
4
+ require 'elasticsearch'
5
+ require 'time'
6
+ require 'socket'
7
+
8
+ module ElasticResults
9
+ module RSpec
10
+ # @private
11
+ class Formatter < ::RSpec::Core::Formatters::BaseFormatter
12
+ ::RSpec::Core::Formatters.register self, :example_passed, :example_failed, :close
13
+
14
+ def initialize(output)
15
+ super
16
+ end
17
+
18
+ def example_passed(notification)
19
+ record_example notification.example
20
+ end
21
+
22
+ def example_failed(notification)
23
+ record_example notification.example
24
+ end
25
+
26
+ def record_example(example)
27
+ ElasticResults.index_result result_for(example)
28
+ end
29
+
30
+ def close(_notification)
31
+ ElasticResults.write_urls
32
+ end
33
+
34
+ private
35
+
36
+ def result_for(example)
37
+ Result.new.tap do |res|
38
+ res.id = example.id
39
+ res.feature_name = example.description
40
+ res.scenario_name = example.full_description
41
+ res.uri = example.location
42
+ res.runtime = example.metadata[:execution_result].run_time
43
+ unless example.exception.nil?
44
+ res.exception_class = example.exception.class.name
45
+ res.exception_msg = example.exception.message
46
+ res.exception_stacktrace = example.exception.backtrace
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,86 @@
1
+ {
2
+ "mappings": {
3
+ "simplecov": {
4
+ "_timestamp": {
5
+ "enabled": true
6
+ },
7
+ "properties": {
8
+ "@timestamp": {
9
+ "type": "date",
10
+ "format": "dateOptionalTime"
11
+ },
12
+ "environment":{
13
+ "type": "string",
14
+ "index" : "not_analyzed",
15
+ "null_value" : "na"
16
+ },
17
+ "suite_name":{
18
+ "type": "string",
19
+ "null_value" : "na"
20
+ },
21
+ "suite_type":{
22
+ "type": "string",
23
+ "index" : "not_analyzed",
24
+ "null_value" : "na"
25
+ },
26
+ "team":{
27
+ "type": "string",
28
+ "null_value" : "na"
29
+ },
30
+ "build_number": {
31
+ "type": "string",
32
+ "index" : "not_analyzed",
33
+ "null_value" : "na"
34
+ },
35
+ "build_url": {
36
+ "type": "string",
37
+ "index" : "not_analyzed",
38
+ "null_value" : "na"
39
+ },
40
+ "node_name": {
41
+ "type": "string",
42
+ "index" : "not_analyzed",
43
+ "null_value" : "na"
44
+ },
45
+ "job_name": {
46
+ "type": "string",
47
+ "index" : "not_analyzed",
48
+ "null_value" : "na"
49
+ },
50
+ "build_tag": {
51
+ "type": "string",
52
+ "index" : "not_analyzed",
53
+ "null_value" : "na"
54
+ },
55
+ "git_commit": {
56
+ "type": "string",
57
+ "index" : "not_analyzed",
58
+ "null_value" : "na"
59
+ },
60
+ "git_url": {
61
+ "type": "string",
62
+ "index" : "not_analyzed",
63
+ "null_value" : "na"
64
+ },
65
+ "git_branch": {
66
+ "type": "string",
67
+ "null_value" : "na"
68
+ },
69
+ "metrics": {
70
+ "properties": {
71
+ "covered_strength": {
72
+ "type": "float"
73
+ },
74
+ "covered_lines": {
75
+ "type": "integer"
76
+ },
77
+ "total_lines": {
78
+ "type": "integer"
79
+ }
80
+ }
81
+ }
82
+
83
+ }
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "mappings": {
3
+ "test_result": {
4
+ "_timestamp": {
5
+ "enabled": true
6
+ },
7
+ "properties": {
8
+ "@timestamp": {
9
+ "type": "date",
10
+ "format": "dateOptionalTime"
11
+ },
12
+ "environment":{
13
+ "type": "string",
14
+ "index" : "not_analyzed",
15
+ "null_value" : "na"
16
+ },
17
+ "suite_name":{
18
+ "type": "string",
19
+ "null_value" : "na"
20
+ },
21
+ "suite_type": {
22
+ "type": "string",
23
+ "index": "not_analyzed",
24
+ "null_value" : "na"
25
+ },
26
+ "team":{
27
+ "type": "string",
28
+ "null_value" : "na"
29
+ },
30
+ "outcome": {
31
+ "type": "string",
32
+ "index" : "not_analyzed",
33
+ "null_value" : "na"
34
+ },
35
+ "exception_msg": {
36
+ "type": "string"
37
+ },
38
+ "exception_class": {
39
+ "type": "string",
40
+ "null_value" : "na"
41
+ },
42
+ "exception_stacktrace": {
43
+ "type": "string",
44
+ "null_value" : "na"
45
+ },
46
+ "runtime" : {
47
+ "type": "float"
48
+ },
49
+ "build_number": {
50
+ "type": "string",
51
+ "index" : "not_analyzed",
52
+ "null_value" : "na"
53
+ },
54
+ "build_url": {
55
+ "type": "string",
56
+ "index" : "not_analyzed",
57
+ "null_value" : "na"
58
+ },
59
+ "node_name": {
60
+ "type": "string",
61
+ "index" : "not_analyzed",
62
+ "null_value" : "na"
63
+ },
64
+ "job_name": {
65
+ "type": "string",
66
+ "index" : "not_analyzed",
67
+ "null_value" : "na"
68
+ },
69
+ "build_tag": {
70
+ "type": "string",
71
+ "index" : "not_analyzed",
72
+ "null_value" : "na"
73
+ },
74
+ "git_commit": {
75
+ "type": "string",
76
+ "index" : "not_analyzed",
77
+ "null_value" : "na"
78
+ },
79
+ "git_url": {
80
+ "type": "string",
81
+ "index" : "not_analyzed",
82
+ "null_value" : "na"
83
+ },
84
+ "git_branch": {
85
+ "type": "string",
86
+ "index" : "not_analyzed",
87
+ "null_value" : "na"
88
+ },
89
+ "tags": {
90
+ "type": "string"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,49 @@
1
+ require 'simplecov'
2
+ require 'elastic_results'
3
+
4
+ module ElasticResults
5
+ module SimpleCov
6
+ # A simple formatter that converts a SimpleCov Result to a hash to be stored in elastic search
7
+ class Formatter
8
+ def format(result)
9
+ data = base_info
10
+
11
+ data[:command_name] = result.command_name
12
+ data[:metrics] = {
13
+ covered_percent: result.covered_percent,
14
+ covered_strength: result.covered_strength.nan? ? 0.0 : result.covered_strength,
15
+ covered_lines: result.covered_lines,
16
+ total_lines: result.total_lines
17
+ }
18
+
19
+ ElasticResults.with_disabled_mocks {
20
+ if ElasticResults.use_unsafe_index
21
+ ElasticResults.unsafe_index ElasticResults.es_index_coverage, ElasticResults.es_type_coverage, data
22
+ else
23
+ ElasticResults.client.index index: ElasticResults.es_index_coverage, type: ElasticResults.es_type_coverage, body: data
24
+ end
25
+ }
26
+ end
27
+
28
+ private
29
+
30
+ def base_info
31
+ {
32
+ '@timestamp' => Time.now.iso8601(3),
33
+ build_number: ENV['BUILD_NUMBER'] ? ENV['BUILD_NUMBER'] : ElasticResults.build_id,
34
+ build_url: ENV['BUILD_URL'],
35
+ node_name: ENV['NODE_NAME'] ? ENV['NODE_NAME'] : Socket.gethostname,
36
+ job_name: ENV['JOB_NAME'],
37
+ build_tag: ENV['BUILD_TAG'],
38
+ environment: ENV['TEST_ENV'] || ENV['RAILS_ENV'],
39
+ git_commit: ElasticResults.git_commit,
40
+ git_url: ElasticResults.git_url,
41
+ git_branch: ElasticResults.git_branch,
42
+ suite_name: ElasticResults.suite_name,
43
+ suite_type: ElasticResults.suite_type,
44
+ team: ElasticResults.team_name
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ # Our root namespace for all objects
2
+ module ElasticResults
3
+ VERSION = '1.0.2'
4
+ end
@@ -0,0 +1,176 @@
1
+ require 'elastic_results/version'
2
+ require 'elastic_results/result'
3
+ require 'elasticsearch'
4
+ require 'json'
5
+
6
+ require "net/https"
7
+ require "uri"
8
+
9
+
10
+
11
+ # Our root namespace for all objects
12
+ module ElasticResults
13
+ class << self
14
+ attr_accessor :suite_name
15
+ attr_accessor :suite_type
16
+ attr_accessor :es_url
17
+ attr_accessor :es_index_result
18
+ attr_accessor :es_index_coverage
19
+ attr_accessor :es_type_result
20
+ attr_accessor :es_type_coverage
21
+ attr_accessor :es_log
22
+ attr_accessor :team_name
23
+ attr_accessor :build_id
24
+ attr_accessor :google_api_key
25
+ attr_accessor :kibana_url
26
+ attr_accessor :use_unsafe_index
27
+ end
28
+
29
+ # Return the mapping JSON for used in creating indicies
30
+ # the which paramter should map to to a filename in the setup folder
31
+ def self.mapping_for(which)
32
+ setup_folder = File.expand_path('./elastic_results/setup', File.dirname(__FILE__))
33
+ File.read("#{setup_folder}/#{which.to_s}.json")
34
+ end
35
+
36
+ # Memoized instance of the elasticsearch client
37
+ def self.client
38
+ @@client ||= Elasticsearch::Client.new url: ElasticResults.es_url, log: ElasticResults.es_log
39
+ end
40
+
41
+ # Given a payload store it as a test result
42
+ def self.index_result(payload)
43
+ index ElasticResults.es_index_result, ElasticResults.es_type_result, payload
44
+ end
45
+
46
+ # Given a payload store it as coverage data
47
+ def self.index_coverage(payload)
48
+ index ElasticResults.es_index_coverage, ElasticResults.es_type_coverage, payload
49
+ end
50
+
51
+ # Generic index function that knows when to use the elasticsearch API.
52
+ def self.index(index, type, payload)
53
+ begin
54
+ with_disabled_mocks {
55
+ if ElasticResults.use_unsafe_index
56
+ unsafe_index index, type, payload.to_hash
57
+ else
58
+ client.index index: index, type: type, body: payload.to_hash
59
+ end
60
+ }
61
+ rescue Exception => ex
62
+ puts "#{ex.message} while storing results in Elasticsearch"
63
+ end
64
+
65
+
66
+ end
67
+
68
+ # An "unsafe" index routine which uses Net:HTTP with SSL validation turned off. Needed for when WebMock is in use
69
+ # as it breaks SSL cert validations.
70
+ def self.unsafe_index(index, type, payload)
71
+ uri = URI.parse(ElasticResults.es_url)
72
+ http = Net::HTTP.new(uri.host, uri.port)
73
+
74
+ if ElasticResults.es_url =~ /https/
75
+ http.use_ssl = true
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
77
+ end
78
+
79
+ request = Net::HTTP::Post.new("/#{index}/#{type}")
80
+ request.body = payload.to_json
81
+ response = http.request(request)
82
+ end
83
+
84
+ # Write out a tab deliminated file containing links to the build information
85
+ def self.write_urls(filename = 'build_links.txt')
86
+ File.open(filename, 'w') { |file| file.write("View failures in Kibana\t#{ElasticResults.fails_url}\t\nView all results in Kibana\t#{ElasticResults.results_url}\t") }
87
+ end
88
+
89
+ # Returns a link to the results. shortened URL if possible otherwise the full URL from long_results_url.
90
+ def self.results_url
91
+ if ElasticResults.google_api_key.nil?
92
+ long_results_url
93
+ else
94
+ require 'googl'
95
+ Googl.shorten(long_results_url, '127.0.0.1', ElasticResults.google_api_key).short_url
96
+ end
97
+ end
98
+
99
+ # Returns a link to the failures. shortened URL if possible otherwise the full URL from long_fails_url.
100
+ def self.fails_url
101
+ if ElasticResults.google_api_key.nil?
102
+ long_fails_url
103
+ else
104
+ require 'googl'
105
+ Googl.shorten(long_fails_url, '127.0.0.1', ElasticResults.google_api_key).short_url
106
+ end
107
+ end
108
+
109
+
110
+ # Set up defaults for the meta data
111
+ ElasticResults.team_name ||= ENV['TEAM_NAME'].nil? ? '???' : ENV['TEAM_NAME']
112
+ ElasticResults.suite_name ||= ENV['SUITE_NAME'].nil? ? File.basename(Dir.pwd) : ENV['SUITE_NAME']
113
+ ElasticResults.suite_type ||= ENV['SUITE_TYPE'].nil? ? 'integration' : ENV['SUITE_TYPE']
114
+ ElasticResults.es_url ||= ENV['ES_HOST'].nil? ? 'http://localhost' : ENV['ES_HOST']
115
+ ElasticResults.es_index_result ||= ENV['ES_INDEX_RESULT'].nil? ? "test_results-#{DateTime.now.strftime('%F')}" : ENV['ES_INDEX_RESULT']
116
+ ElasticResults.es_type_result ||= ENV['ES_TYPE_RESULT'].nil? ? 'test_result' : ENV['ES_TYPE_RESULT']
117
+ ElasticResults.es_log ||= ENV['DEBUG'].nil? ? false : true
118
+ ElasticResults.build_id ||= DateTime.now.strftime('%Y%m%d%H%M%S%L')
119
+ ElasticResults.es_index_coverage ||= ENV['ES_INDEX_COVERAGE'].nil? ? "coverage-#{DateTime.now.strftime('%F')}" : ENV['ES_INDEX_COVERAGE']
120
+ ElasticResults.es_type_coverage ||= ENV['ES_TYPE_COVERAGE'].nil? ? 'simplecov' : ENV['ES_TYPE_COVERAGE']
121
+ ElasticResults.google_api_key ||= ENV['GOOGLE_API_KEY']
122
+ ElasticResults.kibana_url ||= ENV['KIBANA_URL'].nil? ? 'http://localhost:5601' : ENV['KIBANA_URL']
123
+ ElasticResults.use_unsafe_index ||= defined?(WebMock) ? true : false
124
+
125
+
126
+ private
127
+ # Returns a full link to the discover page showing the test results for the current build
128
+ def self.long_results_url
129
+ build_id = ENV['BUILD_NUMBER'] ? ENV['BUILD_NUMBER'] : ElasticResults.build_id
130
+ "#{ElasticResults.kibana_url}/#/discover/?_g=(refreshInterval:(display:Off,pause:!f,section:0,value:0),time:(from:now%2Fd,mode:quick,to:now%2Fd))&_a=(columns:!(team,suite_name,build_number,environment,runtime,outcome,feature_name,scenario_name,exception_class,exception_msg,exception_stacktrace),filters:!(),index:'test_results-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'build_number:#{build_id}')),sort:!('@timestamp',desc))"
131
+ end
132
+
133
+ # Returns a full link to the discover page showing the test failures for the current build
134
+ def self.long_fails_url
135
+ build_id = ENV['BUILD_NUMBER'] ? ENV['BUILD_NUMBER'] : ElasticResults.build_id
136
+ "#{ElasticResults.kibana_url}/#/discover/?_g=(refreshInterval:(display:Off,pause:!f,section:0,value:0),time:(from:now%2Fd,mode:quick,to:now%2Fd))&_a=(columns:!(team,suite_name,build_number,environment,runtime,outcome,feature_name,scenario_name,exception_class,exception_msg,exception_stacktrace),filters:!(),index:'test_results-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'build_number:#{build_id}%20AND%20(outcome:broke%20OR%20outcome:fail)')),sort:!('@timestamp',desc))"
137
+ end
138
+
139
+ # Memoize the git_commit to avoid hitting git for each result
140
+ def self.git_commit
141
+ @@git_commit ||= ENV['GIT_COMMIT'] ? ENV['GIT_COMMIT'] : `git rev-parse HEAD`.chomp
142
+ end
143
+
144
+ # Memoize the git_url to avoid hitting git for each result
145
+ def self.git_url
146
+ @@git_url ||= ENV['GIT_URL'] ? ENV['GIT_URL'] : `git config --get remote.origin.url`.chomp
147
+ end
148
+
149
+ # Memoize the git_branch to avoid hitting git for each result
150
+ def self.git_branch
151
+ @@git_branch ||= ENV['GIT_BRANCH'] ? ENV['GIT_BRANCH'] : `git rev-parse --abbrev-ref HEAD`.chomp
152
+ end
153
+
154
+ # Helper function to disable mocks before calling out to the web (and getting blocked)
155
+ def self.with_disabled_mocks
156
+ should_reenable_webmock = false
157
+ if defined? WebMock
158
+ if !WebMock.net_connect_allowed?
159
+ should_reenable_webmock = true
160
+ WebMock.disable!
161
+ end
162
+ end
163
+
164
+ if defined? VCR
165
+ VCR.turned_off {
166
+ yield
167
+ }
168
+ else
169
+ yield
170
+ end
171
+
172
+ if should_reenable_webmock
173
+ WebMock.enable!
174
+ end
175
+ end
176
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elastic_results
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Donavan Stanley
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: elasticsearch
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: googl
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - dstanley@covermymeds.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - LICENSE
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - elastic_results.gemspec
99
+ - lib/elastic_results.rb
100
+ - lib/elastic_results/cucumber.rb
101
+ - lib/elastic_results/result.rb
102
+ - lib/elastic_results/rspec.rb
103
+ - lib/elastic_results/setup/coverage.json
104
+ - lib/elastic_results/setup/test_results.json
105
+ - lib/elastic_results/simplecov.rb
106
+ - lib/elastic_results/version.rb
107
+ homepage: https://github.com/covermymeds/elastic_results
108
+ licenses: []
109
+ metadata:
110
+ allowed_push_host: https://rubygems.org
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.0.14
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Formatters to shove test results into elastic search
131
+ test_files: []