rspec2db 1.2.0

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: 46aed6e716b94e2dffdba843c02777e90583f97e
4
+ data.tar.gz: c75c5d8efa246d8b7c6f8878087df2c8397e6a61
5
+ SHA512:
6
+ metadata.gz: b5cc9b7c8f0ce9c459375c55d6ecc5c88a25b492c023158838e071e00b580bec846b694f38a6b3eb8438ee54f7676cf4692405ab09c66db150203351a08ff262
7
+ data.tar.gz: af16bda7290716021632416ab48cda8eef50a09176575d539b0ea4d35a91ab332e13f27f0cf6716f8e9736fa5b6748247c1d267b8ed72159b2c5ede5c51b8139
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/.project ADDED
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>rspec2db git</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ </buildSpec>
9
+ <natures>
10
+ <nature>com.aptana.ruby.core.rubynature</nature>
11
+ </natures>
12
+ </projectDescription>
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format Rspec2db
2
+ --options ./config/rspec2db.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rspec2db (1.2.0)
5
+ activerecord (~> 5.1)
6
+ activerecord-postgresql-adapter
7
+ pg (~> 0.21)
8
+ rspec (>= 3.0)
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ activemodel (5.2.0)
14
+ activesupport (= 5.2.0)
15
+ activerecord (5.2.0)
16
+ activemodel (= 5.2.0)
17
+ activesupport (= 5.2.0)
18
+ arel (>= 9.0)
19
+ activerecord-postgresql-adapter (0.0.1)
20
+ pg
21
+ activesupport (5.2.0)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 0.7, < 2)
24
+ minitest (~> 5.1)
25
+ tzinfo (~> 1.1)
26
+ arel (9.0.0)
27
+ concurrent-ruby (1.0.5)
28
+ diff-lcs (1.3)
29
+ i18n (1.0.1)
30
+ concurrent-ruby (~> 1.0)
31
+ minitest (5.11.3)
32
+ pg (0.21.0)
33
+ rspec (3.7.0)
34
+ rspec-core (~> 3.7.0)
35
+ rspec-expectations (~> 3.7.0)
36
+ rspec-mocks (~> 3.7.0)
37
+ rspec-core (3.7.1)
38
+ rspec-support (~> 3.7.0)
39
+ rspec-expectations (3.7.0)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.7.0)
42
+ rspec-mocks (3.7.0)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.7.0)
45
+ rspec-support (3.7.1)
46
+ thread_safe (0.3.6)
47
+ tzinfo (1.2.5)
48
+ thread_safe (~> 0.1)
49
+
50
+ PLATFORMS
51
+ ruby
52
+
53
+ DEPENDENCIES
54
+ rspec2db!
55
+
56
+ BUNDLED WITH
57
+ 1.16.1
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ rspec2db: RSpec DB Formatter
2
+ ============================
3
+
4
+
5
+ ## Description
6
+ rspec2db is a Ruby gem used for storing rspec test execution results to a database. This gem extends RSpec formatter and has to be required in `.rspec` (which is done by rspec2db init command described below). Once added, the gem will handle storing all test related informations (test suites, test cases, test steps) into the database. By default, rspec2db supports writing to postgresql database but can be easily adjusted to write results into other relational databases as well since it uses ActiveRecord for persisting data.
7
+
8
+ The gem also provides a CLI tool meant for bootstrapping rspec2db configuration files.
9
+
10
+
11
+ ## Rspec2DB CLI
12
+
13
+ Rspec2db provides a CLI tool that helps users with bootstraping by generating configuration files.
14
+
15
+ ```
16
+ $ rspec2db
17
+ Loading default rspec2db configuration.
18
+ Using project config file
19
+ No command provided
20
+ Usage: rspec2db [options] <command>
21
+
22
+ init - initialize rspec2db config file (~/rspec2db.yaml)
23
+ create - creates rspec2db database
24
+ migrate - migrates rspec2db database
25
+ build-stats - extracts test run execution based on test run id
26
+
27
+ Options:
28
+ -H, --host=h rspec2db database host
29
+ -p, --port=port rspec2db database port
30
+ -d, --database=db rspec2db database name
31
+ -u, --username=u rspec2db database username
32
+ -w, --password=pw rspec2db database user password
33
+ -a, --adapter=ad rspec2db database adapter
34
+ -h, --help rspec2db help
35
+ ```
36
+ ### Usage
37
+ The tool provides number of options for onboarding:
38
+ 1. `init` - Initialization of a global configuration file (stored in the home directory) with predefined configuration that can be modified (ie. values in `~/rspec2db.yaml` should be changed to appropriate values). This will also add Rspec2db formater to your spec `.rspec` file.
39
+ 2. `create` - Rake-like task that will create and seed the db.
40
+ 3. `migrate` - Rake-like task that will migrate latest changes to the db
41
+ 3. `build-stats` - Export of Test execution results and statistics for a Test build
42
+
43
+ ### Example
44
+
45
+ ```
46
+ $ rspec2db init
47
+ Loading default rspec2db configuration.
48
+ Using project config file
49
+ No rspec2db config specified in .rspec
50
+ Loading default rspec2db options (/Users/user/rspec2db.yaml)
51
+ Creating default rspec2db config file (/Users/user/rspec2db.yaml)
52
+ Adding rspec2db options and formatter to .rspec
53
+ ```
54
+
55
+ ```
56
+ # Edit ~/rspec2db.yaml to use your PG database. Database has to exist before running create task.
57
+ $ rspec2db create
58
+ Loading default rspec2db configuration.
59
+ No project file detected. Looking for local config file
60
+ -- create_table(:test_suites, {:force=>:cascade})
61
+ -> 0.1380s
62
+ -- create_table(:test_runs, {:force=>:cascade})
63
+ -> 0.0513s
64
+ -- create_table(:test_cases, {:force=>:cascade})
65
+ -> 0.0425s
66
+ ```
67
+
68
+ ```
69
+ # Migrate changes
70
+ $ rspec2db migrate
71
+ Loading default rspec2db configuration.
72
+ No project file detected. Looking for local config file
73
+ ```
74
+
75
+ This flow will result in a configured PG Database and a configuration file that can be used among different projects.
76
+
77
+
78
+ To reuse the existing configuration file in a separate rspec spec project, simply run `rspec init`. This will configure your `.rspec` file:
79
+ `
80
+ ```$ rspec2db init
81
+ bundle exec rspec2db init
82
+ Loading default rspec2db configuration.
83
+ No project file detected. Looking for local config file
84
+ No rspec2db config specified in .rspec
85
+ Loading default rspec2db options (/Users/user/rspec2db.yaml)
86
+ Default config file exists, override with default? (Y/N)
87
+ N
88
+ Adding rspec2db options and formatter to .rspec
89
+ ```
90
+
91
+ ## Other
92
+ Rspec2DB CLI commands load the rspec2db configuration file that is either defined in the `.rspec` file in your spec directory, or loads the default configuration file in your home directory.
93
+ Default configuration (from the `rspec2db.yaml`) can be overriden using the provided CLI options for targeting a specific DB (ie. by providing host, port, db credentials and similar).
94
+
95
+ ### Using the formatter
96
+ `rspec2db init` will add `rspec2db` formatter to `.rspec` file, resulting in every spec that is ran by `rspec spec/*` to be written to the database (in the format of test suites, test cases and test steps).
97
+
98
+ #### Retrieve results from database
99
+
100
+ To retrieve test results from the database, you can use RSpec2DB CLI command `build-stats`.
101
+ To execute the script, run the following command:
102
+ ```
103
+ $ rspec2db build-stats --help
104
+ Loading default rspec2db configuration.
105
+ No project file detected. Looking for local config file
106
+ Usage: rspec2db [options]
107
+ -i, --id=id rspec2db test build id
108
+ -o, --output file rspec2db extract output file
109
+ -l, --limit [LIMIT] rspec2db number of extracted test runs
110
+ -s, --suite suite rspec2db test suite
111
+ -U [url]--URL [url] Test Reporter (www.github.com/ATLANTBH/owl) url link
112
+
113
+ ```
114
+
115
+
116
+ To execute the script with all optional parameters, run the following command:
117
+
118
+ ```
119
+ rspec2db build-stats -i <build_number> -l <limit> -o <output_file> -s <test_suite> -U <reporter_url>
120
+ ```
121
+
122
+ - build_number is a user specified value which needs to be the same like the one found in rspec2db.yml configuration file
123
+ - output_file is name of the file in which results are written
124
+ - limit (optional) determines if the statistics will be calculated for a single run with same build_number or for all runs with same build_number:
125
+ - not specified - results for only one run (the first one) will be in the results file
126
+ - all - results for all runs will be in the results file
127
+ - test_suite is the name of the suite from which we want to query results
128
+ - reporter_url (optional) is the url of the Test Reporter tool which can be used in conjunction with rspec2db gem. If you use this parameter, you need to specify base url of your Test Reporter instance (for example: http://testreporter:8080). Based on this url, script will generate exact url path to this specific run which contains list of tests that have been executed
129
+ - suite_name - name of the test suite that was specified in `rspec2db.yml` file and for which the stats are calculated
130
+
131
+
132
+ ### Updating Rspec2DB gem version
133
+
134
+ If you are updating Rspec2DB gem from a to a newer version, running `rspec2db migrate` is required. This command will migrate the database so that it satisfies the Rspec2DB model, if it was changed.
135
+
136
+ ```
137
+ $ bundle update rspec2db
138
+ $ bundle exec rspec2db migrate
139
+ ```
data/bin/rspec2db ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require_relative '../lib/rspec2db/utils/db_utils'
6
+ require_relative '../lib/rspec2db/utils/rspec_configuration_helper'
7
+
8
+ def check_required_options(required_options, options)
9
+ missing_options = required_options - options.keys.map { |k| k.to_s }
10
+ abort 'Missing required options: ' + missing_options.reduce { |m, k| m + ', ' + k } unless missing_options.empty?
11
+ end
12
+
13
+ def parse_extract_command
14
+ options = {}
15
+ required_options = ['id', 'file', 'suite']
16
+ extract_options = OptionParser.new do |opts|
17
+ opts.on('-i id', '--id=id', 'rspec2db test build id') do |id|
18
+ options[:id] = id
19
+ end
20
+
21
+ opts.on('-o file', '--output file', 'rspec2db extract output file') do |f|
22
+ options[:file] = f
23
+ end
24
+
25
+ opts.on('-l [LIMIT]', '--limit [LIMIT]', 'rspec2db number of extracted test runs') do |l|
26
+ options[:limit] = l || nil
27
+ end
28
+
29
+ opts.on('-s suite', '--suite suite', 'rspec2db test suite') do |suite|
30
+ options[:suite] = suite
31
+ end
32
+
33
+ opts.on('-U [url]' '--URL [url]', 'Test Reporter (www.github.com/ATLANTBH/owl) url link') do |url|
34
+ options[:url] = url
35
+ end
36
+ end
37
+ extract_options.parse!
38
+ check_required_options(required_options, options)
39
+ [options, extract_options]
40
+ end
41
+
42
+ def parse_options(load_local = true)
43
+ options = {}
44
+ required_options = ['username', 'password', 'host', 'port', 'database']
45
+
46
+ option_parser = OptionParser.new do |opts|
47
+ opts.banner = "Usage: rspec2db [options] <command>\n" + \
48
+ "\ninit - initialize rspec2db config file (" + ENV['PWD'] + "/rspec2db.yaml)" \
49
+ "\ncreate - creates rspec2db database" + \
50
+ "\nmigrate - migrates rspec2db database" + \
51
+ "\nbuild-stats - extracts test run execution based on test run id" + \
52
+ "\n\nOptions:"
53
+
54
+ opts.on('-H h', '--host=h', 'rspec2db database host') do |h|
55
+ options[:host] = h
56
+ end
57
+
58
+ opts.on('-p port', '--port=port', 'rspec2db database port') do |port|
59
+ options[:port] = port
60
+ end
61
+
62
+ opts.on('-d db', '--database=db', 'rspec2db database name') do |db|
63
+ options[:database] = db
64
+ end
65
+
66
+ opts.on('-u u', '--username=u', 'rspec2db database username') do |u|
67
+ options[:username] = u
68
+ end
69
+
70
+ opts.on('-w pw', '--password=pw', 'rspec2db database user password') do |pw|
71
+ options[:password] = pw
72
+ end
73
+
74
+ opts.on('-a ad', '--adapter=ad', 'rspec2db database adapter') do |ad|
75
+ options[:adapter] = ad
76
+ end
77
+
78
+ opts.on('-h', '--help', 'rspec2db help') do |h|
79
+ options[:help] = h
80
+ end
81
+ end
82
+ option_parser.order!
83
+ return [options, option_parser] unless options.empty?
84
+
85
+ if options.empty?
86
+ puts 'Loading rspec2db configuration.'
87
+ options = RSpecConfigurationHelper.load_local_config
88
+ end
89
+
90
+ check_required_options(required_options, options)
91
+ options[:adapter] = 'postgresql' if options[:adapter].nil?
92
+
93
+ [options, option_parser]
94
+ end
95
+
96
+ options, option_parser = parse_options
97
+ command = ARGV[0]
98
+ case command
99
+ when 'init'
100
+ puts 'No .rspec file found in working directory' if options.nil?
101
+ when 'create'
102
+ DBUtils.create_rspec_db options
103
+ when 'migrate'
104
+ DBUtils.migrate_rspec_db options
105
+ when 'build-stats'
106
+ build_options, extract_option_parser = parse_extract_command
107
+ if build_options.empty?
108
+ puts extract_option_parser
109
+ else
110
+ DBUtils.build_stats build_options[:id], build_options[:file], build_options[:limit], build_options[:suite], options, build_options[:url]
111
+ end
112
+ when nil
113
+ puts 'No command provided'
114
+ puts option_parser
115
+ else
116
+ puts 'Command unknown.'
117
+ puts option_parser
118
+ end
@@ -0,0 +1,13 @@
1
+ options:
2
+ suite: 'suite name'
3
+ build: '1' #
4
+ backtrace: false
5
+ git_commit:
6
+ git_branch:
7
+ dbconnection:
8
+ adapter: postgresql
9
+ database: rspec
10
+ username: postgres
11
+ password: test123!
12
+ host: localhost
13
+ port: 5432
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'active_record'
4
+
5
+ dbconfig = YAML::load(File.open('./rspec2db.yml'))
6
+ ActiveRecord::Base.establish_connection(dbconfig["dbconnection"])
7
+
8
+ ActiveRecord::Base.transaction do
9
+
10
+ ActiveRecord::Migration.create_table :test_suites do |t|
11
+ t.string :suite
12
+ t.timestamps
13
+ end
14
+
15
+ ActiveRecord::Migration.create_table :test_runs do |t|
16
+ t.float :duration
17
+ t.integer :example_count
18
+ t.integer :failure_count
19
+ t.integer :pending_count
20
+ t.string :build
21
+ t.string :computer_name
22
+ t.string :environment
23
+ t.string :git_hash
24
+ t.string :git_branch
25
+ t.timestamps
26
+ t.references :test_suites
27
+ end
28
+
29
+ ActiveRecord::Migration.create_table :test_cases do |t|
30
+ t.string :test_group
31
+ t.string :context
32
+ t.string :description
33
+ t.string :execution_result
34
+ t.string :screenshot_path
35
+ t.string :screenshot_url
36
+ t.text :exception
37
+ t.string :pending_message
38
+ t.float :duration
39
+ t.text :backtrace
40
+ t.text :metadata
41
+ t.timestamps
42
+ t.references :test_runs
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'active_record'
4
+
5
+ module DBUtils
6
+ def self.build_stats(build_id, file_name, limit, suite, config, reporter_url = 'http://localhost/')
7
+ ActiveRecord::Base.establish_connection(config)
8
+ if limit == nil
9
+ @query = "select tr.* from test_runs tr, test_suites ts where ts.id = tr.test_suites_id and tr.build LIKE '#{build_id}' and ts.suite LIKE '#{suite}' order by tr.created_at desc limit 1"
10
+ elsif limit == 'all'
11
+ @query = "select tr.* from test_runs tr, test_suites ts where ts.id = tr.test_suites_id and tr.build LIKE '#{build_id}' and ts.suite LIKE '#{suite}' order by tr.created_at desc"
12
+ else
13
+ raise Exception, 'Invalid parameter value.'
14
+ end
15
+
16
+ report = "Execution stats:\n"
17
+ report << "----------------\n"
18
+
19
+ @build = ""
20
+ @success_rate = []
21
+ @test_steps_count = 0
22
+ @test_steps_pass_count = 0
23
+ @test_steps_failed_count = 0
24
+ @duration = 0
25
+ @test_run_id = 0
26
+
27
+ @test_runs = TestRun.find_by_sql(@query)
28
+ @test_runs.each do |test_run|
29
+ @success_rate << ((test_run.example_count.to_i - test_run.failure_count.to_i).to_f / test_run.example_count.to_f) * 100
30
+ @test_steps_count = @test_steps_count + test_run.example_count
31
+ @test_steps_pass_count = @test_steps_pass_count + (test_run.example_count.to_i - test_run.failure_count.to_i)
32
+ @test_steps_failed_count = @test_steps_failed_count + test_run.failure_count
33
+ if test_run.duration>@duration
34
+ @duration = test_run.duration
35
+ end
36
+ @build = test_run.build
37
+ @test_run_id = test_run.id
38
+ end
39
+
40
+ @rate = ( @test_steps_pass_count.to_f / @test_steps_count.to_f ) * 100
41
+ @formatted_rate = sprintf('%.2f', @rate.to_f)
42
+ @formatted_duration = sprintf('%.2f', @duration.to_f)
43
+
44
+ if reporter_url == nil
45
+ File.open(file_name, 'w') { |f| f.write("#{report}Build name: #{@build}\nDuration: #{@formatted_duration}s\nSuccess rate: #{@formatted_rate}%\nTest steps count: #{@test_steps_count}\nTest steps passed: #{@test_steps_pass_count}\nTest steps failed: #{@test_steps_failed_count}\n") }
46
+ else
47
+ File.open(file_name, 'w') { |f| f.write("#{report}Build name: #{@build}\nDuration: #{@formatted_duration}s\nSuccess rate: #{@formatted_rate}%\nTest steps count: #{@test_steps_count}\nTest steps passed: #{@test_steps_pass_count}\nTest steps failed: #{@test_steps_failed_count}\nTest reporter page: #{reporter_url}/test-runs/#{@test_run_id}/test-cases\n") }
48
+ end
49
+
50
+ sql = "select count(tc.test_group) from test_suites ts, test_runs tr, test_cases tc where ts.id=tr.test_suites_id and tr.id=tc.test_runs_id and tr.build='#{build_id}' and ts.suite='#{suite}' group by tc.test_group"
51
+ number_of_scripts = TestRun.find_by_sql(sql)
52
+
53
+ File.open(file_name, 'a') { |f| f.puts "Number of test scripts: #{number_of_scripts.count}"}
54
+
55
+ sql_failed="select count(tc.test_group) from test_suites ts, test_runs tr, test_cases tc where ts.id=tr.test_suites_id and tr.id=tc.test_runs_id and tr.build='#{build_id}' and ts.suite='#{suite}' and tc.execution_result='failed' group by tc.test_group"
56
+
57
+ number_of_failed_scripts=TestRun.find_by_sql(sql_failed)
58
+ File.open(file_name, 'a') { |f| f.puts "Number of failed scripts: #{number_of_failed_scripts.count}"}
59
+ end
60
+ end
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ require "active_record"
3
+
4
+ class AddTestCaseScreenshots < ActiveRecord::Migration[5.2]
5
+ def self.up
6
+ add_column :test_cases, :screenshot_path, :string
7
+ add_column :test_cases, :screenshot_url, :string
8
+ add_column :test_runs, :environment, :string
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'active_record'
4
+
5
+ module DBUtils
6
+ def self.create_rspec_db(dbconfig)
7
+ ActiveRecord::Base.establish_connection(dbconfig)
8
+
9
+ ActiveRecord::Schema.define(version: 1) do
10
+ create_table :test_suites, force: :cascade do |t|
11
+ t.string :suite
12
+ t.timestamps
13
+ end
14
+
15
+ create_table :test_runs, force: :cascade do |t|
16
+ t.float :duration
17
+ t.integer :example_count
18
+ t.integer :failure_count
19
+ t.integer :pending_count
20
+ t.string :build
21
+ t.string :computer_name
22
+ t.string :environment
23
+ t.string :git_hash
24
+ t.string :git_branch
25
+ t.timestamps
26
+ t.references :test_suites
27
+ end
28
+
29
+ create_table :test_cases, force: :cascade do |t|
30
+ t.string :test_group
31
+ t.string :context
32
+ t.string :description
33
+ t.string :execution_result
34
+ t.string :screenshot_path
35
+ t.string :screenshot_url
36
+ t.text :exception
37
+ t.string :pending_message
38
+ t.float :duration
39
+ t.text :backtrace
40
+ t.text :metadata
41
+ t.timestamps
42
+ t.references :test_runs
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.migrate_rspec_db(dbconfig)
48
+ ActiveRecord::Base.establish_connection(dbconfig)
49
+ migrations_path = Bundler.rubygems.find_name('rspec2db').first.full_gem_path + '/lib/rspec2db/db/migrations'
50
+ migration_context = ActiveRecord::MigrationContext.new(migrations_path)
51
+ migration_context.migrate
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ class TestCase < ActiveRecord::Base
2
+ belongs_to :testrun
3
+ end
@@ -0,0 +1,4 @@
1
+ class TestRun < ActiveRecord::Base
2
+ has_many :testcases
3
+ belongs_to :testsuite
4
+ end
@@ -0,0 +1,3 @@
1
+ class TestSuite < ActiveRecord::Base
2
+ has_many :testruns
3
+ end
@@ -0,0 +1,99 @@
1
+ require 'active_record'
2
+ require_relative 'rspec_configuration_helper'
3
+ require_relative '../db/schema.rb'
4
+ require_relative '../db/build_execution_stats'
5
+ require_relative '../models/test_case'
6
+ require_relative '../models/test_run'
7
+ require_relative '../models/test_suite'
8
+
9
+ module DBUtils
10
+ include RSpecConfigurationHelper
11
+
12
+ def connect_to_db(config)
13
+ ActiveRecord::Base.establish_connection(config['dbconnection'])
14
+ ActiveRecord::Base.default_timezone = :local
15
+ end
16
+
17
+ def create_test_suite(config)
18
+ begin
19
+ test_suite = TestSuite.find_or_create_by(suite: config['options']['suite'])
20
+ rescue ActiveRecord::RecordNotUnique => active_record_error
21
+ puts active_record_error
22
+ test_suite = TestSuite.find_by(suite: config['options']['suite'])
23
+ end
24
+ end
25
+
26
+ def create_test_run(test_suite, config, global_file_lock = '/tmp/.rspec2db.yaml')
27
+ test_run_hash = {
28
+ build: config['options']['build'],
29
+ test_suites_id: test_suite.id,
30
+ git_hash: config['options']['git_commit'],
31
+ git_branch: config['options']['git_branch'],
32
+ environment: config['options']['environment']
33
+ }
34
+ global_lock = File.new(global_file_lock, File::CREAT | File::TRUNC)
35
+
36
+ begin
37
+ global_lock.flock(File::LOCK_EX)
38
+ test_run = TestRun.where(test_run_hash).first || TestRun.create(test_run_hash)
39
+ global_lock.flock(File::LOCK_UN)
40
+ rescue Exception => e
41
+ puts e.message
42
+ puts e.backtrace
43
+ ensure
44
+ global_lock.flock(File::LOCK_UN)
45
+ end
46
+ test_run
47
+ end
48
+
49
+ def create_test_case(test_run, example_group, example, backtrace = nil, screenshot_event = nil)
50
+ example
51
+ test_case = TestCase.create(
52
+ test_runs_id: test_run.id,
53
+ test_group: example_group.top_level_description,
54
+ description: example.description,
55
+ execution_result: example.execution_result.status,
56
+ duration: example.execution_result.run_time,
57
+ pending_message: example.execution_result.pending_message.to_s,
58
+ exception: example.execution_result.exception.to_s)
59
+
60
+ if backtrace && !example.execution_result.exception.nil? && !example.execution_result.exception.backtrace.nil?
61
+ File.open('/tmp/output', 'w'){ |w| w.write(example.execution_result.exception.backtrace) }
62
+ test_case.update_attributes(
63
+ backtrace: example.execution_result.exception.backtrace.join('\n'),
64
+ metadata: print_example_failed_content(example)
65
+ )
66
+ end
67
+
68
+ if !example_group.top_level? # check for detecting Context (as opposed to Describe group)
69
+ test_case.update_attributes(
70
+ context: example_group.description
71
+ )
72
+ end
73
+ if screenshot_event[:example] && screenshot_event[:example] == example.description
74
+ screenshot_event.delete :example
75
+ test_case.update_attributes screenshot_event
76
+ screenshot_event = {}
77
+ end
78
+ test_case
79
+ end
80
+
81
+ def update_test_run(test_run, summary, global_file_lock = nil)
82
+ global_lock = File.new(global_file_lock, File::CREAT | File::TRUNC)
83
+ begin
84
+ global_lock.flock(File::LOCK_EX)
85
+ test_run.reload
86
+ test_run.increment(:example_count, summary.example_count)
87
+ .increment(:failure_count, summary.failure_count)
88
+ .increment(:pending_count, summary.pending_count)
89
+ .increment(:duration, summary.duration)
90
+ .save!
91
+ global_lock.flock(File::LOCK_UN)
92
+ rescue Exception => e
93
+ puts e.message
94
+ puts e.backtrace
95
+ ensure
96
+ global_lock.flock(File::LOCK_UN)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,127 @@
1
+ module RSpecConfigurationHelper
2
+ def self.load_config(failsafe = true)
3
+ rspec_file = '.rspec'
4
+ file_path = nil
5
+ File.open(rspec_file).each do |line|
6
+ file_path = line.split('--options ').last.strip if line.match '--options\s?.+ya?ml'
7
+ end
8
+
9
+ if file_path.nil?
10
+ puts 'No rspec2db config specified in .rspec'
11
+ return nil
12
+ elsif !File.exists?(file_path)
13
+ puts 'could not find the config file at the following location: ' + file_path
14
+ abort 'exiting... please check your config file' if failsafe
15
+ nil
16
+ else
17
+ config = YAML::load(File.open(file_path))
18
+ config['file_path'] = file_path
19
+ config
20
+ end
21
+ # Bakir: disabled override by environment variables because it can cause unwanted side-effects
22
+ #(for example: env variable defined by specific name like BUILD can override what user defined in rspec2db.yaml)
23
+ #override_default_config config
24
+ end
25
+
26
+ def self.generate_local_config(rspec2db_config_file)
27
+ if File.exist?(rspec2db_config_file)
28
+ puts 'Default config file exists, override with default? (Y/N)'
29
+ override = STDIN.gets.chomp
30
+ return if override == 'N' || override == 'n'
31
+ end
32
+
33
+ puts 'Creating default rspec2db config file (' + rspec2db_config_file + ')'
34
+ rspec2db_bundler_path = Bundler.rubygems.find_name('rspec2db').first.full_gem_path
35
+ rspec2db_config_src = rspec2db_bundler_path + '/config/rspec2db.yml'
36
+ FileUtils.cp rspec2db_config_src, rspec2db_config_file
37
+ end
38
+
39
+ def self.load_local_config
40
+ rspec2db_default_config_file = './rspec2db.yaml'
41
+ rspec2db_project_config = RSpecConfigurationHelper.load_config
42
+ rspec_options_file = '.rspec'
43
+ unless rspec2db_project_config.nil?
44
+ puts 'Using project config file'
45
+ else
46
+ puts 'No project file specified in .rspec. Looking for default rspec2db.yaml file'
47
+ if File.exists? rspec2db_default_config_file
48
+ rspec2db_project_config = YAML::load(File.open(rspec2db_default_config_file))
49
+ else
50
+ puts 'File not found, generating a default rpsec2db.yaml'
51
+ RSpecConfigurationHelper.generate_local_config(rspec2db_default_config_file)
52
+ rspec2db_project_config = YAML::load(File.open(rspec2db_default_config_file))
53
+ end
54
+ RSpecConfigurationHelper.insert_rspec2db_formatter rspec_options_file, rspec2db_default_config_file
55
+ end
56
+
57
+ Hash[rspec2db_project_config['dbconnection'].map { |k, v| [k.to_sym, v] }]
58
+ end
59
+
60
+ def self.insert_rspec2db_formatter(rspec_file, rspec2db_config_file)
61
+ rspec2db_formatter = "--format Rspec2db\n--options #{rspec2db_config_file}"
62
+ puts 'Adding rspec2db options and formatter to ' + rspec_file
63
+ File.open(rspec_file, 'a') do |f|
64
+ f.puts rspec2db_formatter
65
+ f.flush
66
+ end
67
+ end
68
+
69
+ def self.override_default_config(config)
70
+ config["options"].each do |key, value|
71
+ config["options"][key] = ENV[key.upcase] unless ENV[key.upcase].nil?
72
+ end
73
+
74
+ config["dbconnection"].each do |key, value|
75
+ config["dbconnection"][key] = ENV[key.upcase] unless ENV[key.upcase].nil?
76
+ end
77
+ config
78
+ end
79
+
80
+ def self.check_rspec_options
81
+ rspec_options_file = '.rspec'
82
+ rspec2db_config_file = './rspec2db.yaml'
83
+
84
+ return nil unless File.exist?(rspec_options_file)
85
+ config = RSpecConfigurationHelper.load_config(false)
86
+ if config.nil?
87
+ puts 'Creating rspec2db config file (' + rspec2db_config_file + ')'
88
+ RSpecConfigurationHelper.generate_local_config rspec2db_config_file
89
+ RSpecConfigurationHelper.insert_rspec2db_formatter rspec_options_file, rspec2db_config_file
90
+ return RSpecConfigurationHelper.load_config
91
+ end
92
+ puts 'rspec2db config file already exists (' + config['file_path'] + ')'
93
+ config['dbconnection']
94
+ end
95
+
96
+
97
+ def extract_rspec_core_version
98
+ Gem.loaded_specs['rspec-core'].version.to_s.split('.').map { |v| v.to_i }
99
+ end
100
+
101
+ def load_snippet_extractor
102
+ major_version, minor_version = @rspec_core_version
103
+
104
+ if major_version == 3 && minor_version < 4
105
+ require 'rspec/core/formatters/snippet_extractor'
106
+ RSpec::Core::Formatters::SnippetExtractor.new
107
+ elsif major_version == 3 && minor_version >= 4
108
+ require 'rspec/core/formatters/html_snippet_extractor'
109
+ RSpec::Core::Formatters::HtmlSnippetExtractor.new
110
+ end
111
+ end
112
+
113
+ def print_example_failed_content(example)
114
+ print_content = ''
115
+ exception = example.execution_result.exception
116
+ return print_content if exception.backtrace.nil?
117
+
118
+ backtrace_content = exception.backtrace.map { |line| RSpec::Core::BacktraceFormatter.new.backtrace_line(line) }
119
+ backtrace_content.compact!
120
+ snippet_extractor ||= load_snippet_extractor
121
+
122
+ snippet_content = snippet_extractor.snippet(backtrace_content)
123
+ snippet_content = snippet_content.sub( "class=\"offending\"", "class=\"offending\" style=\"background-color: red;\"" )
124
+ print_content = " <pre class=\"ruby\" style=\"background-color: #E6E6E6; border: 1px solid;\"><code>#{snippet_content}</code></pre>"
125
+ print_content
126
+ end
127
+ end
data/lib/rspec2db.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'rspec/core/formatters/base_text_formatter'
2
+ require 'active_record'
3
+ require 'yaml'
4
+ require_relative './rspec2db/utils/db_utils'
5
+ require_relative './rspec2db/utils/rspec_configuration_helper'
6
+
7
+
8
+ class Rspec2db < RSpec::Core::Formatters::BaseTextFormatter
9
+ include DBUtils
10
+ include RSpecConfigurationHelper
11
+
12
+ RSpec::Core::Formatters.register self, :start,
13
+ :example_group_started,
14
+ :example_started,
15
+ :example_passed,
16
+ :example_pending,
17
+ :example_failed,
18
+ :dump_failures,
19
+ :dump_pending,
20
+ :dump_summary,
21
+ :dump_profile,
22
+ :screenshot_saved,
23
+ :screenshot_uploaded
24
+
25
+ attr_reader :output,
26
+ :results,
27
+ :config,
28
+ :example_group,
29
+ :global_file_lock,
30
+ :test_run,
31
+ :test_suite
32
+
33
+
34
+ def initialize(output)
35
+ @output = output || StringIO.new
36
+ @global_file_lock = '/tmp/rspec2db.lock'
37
+ @config = RSpecConfigurationHelper.load_config
38
+ connect_to_db @config
39
+ @test_suite = create_test_suite(@config)
40
+ @test_run = create_test_run(@test_suite, @config)
41
+ @screenshot_event = {}
42
+ end
43
+
44
+ def start(notification)
45
+ end
46
+
47
+ def example_group_started(notification)
48
+ @example_group = notification.group
49
+ end
50
+
51
+ def example_group_finished(notification)
52
+ end
53
+
54
+ def example_started(example)
55
+ end
56
+
57
+ def example_passed(notification)
58
+
59
+ @current_test_case = create_test_case(@test_run, @example_group, notification.example, @config['options']['backtrace'], @screenshot_event)
60
+ end
61
+
62
+ def example_pending(notification)
63
+ @current_test_case = create_test_case(@test_run, @example_group, notification.example, @config['options']['backtrace'], @screenshot_event)
64
+ end
65
+
66
+ def example_failed(notification)
67
+ @current_test_case = create_test_case(@test_run, @example_group, notification.example, @config['options']['backtrace'], @screenshot_event)
68
+ end
69
+
70
+ def message(notification)
71
+ end
72
+
73
+ def start_dump(notification)
74
+ end
75
+
76
+ def dump_pending(notification)
77
+ end
78
+
79
+ def dump_profile(notification)
80
+ end
81
+
82
+ def dump_failures(notification)
83
+ end
84
+
85
+ def screenshot_saved(notification)
86
+ @screenshot_event.merge!(
87
+ screenshot_path: notification[:screenshot_path],
88
+ example: notification[:example]
89
+ )
90
+ end
91
+
92
+ def screenshot_uploaded(notification)
93
+ @screenshot_event.merge!(
94
+ screenshot_url: notification[:screenshot_url],
95
+ example: notification[:example]
96
+ )
97
+ end
98
+
99
+ def dump_summary(notification)
100
+ update_test_run(@test_run, notification, @global_file_lock)
101
+ end
102
+
103
+ def seed(seed)
104
+ end
105
+
106
+ end
data/rspec2db.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rspec2db'
3
+ s.version = '1.2.0'
4
+ s.date = '2018-01-01'
5
+ s.summary = 'Save your RSpec test results to a database'
6
+ s.description = 'A simple RSpec formatter to enable writing RSpec test results to any database using ActiveRecord. Specify DB connection in a yml file and put path to that file as --options PATH in your .rspec file'
7
+ s.authors = ['Nermin Caluk', 'Bakir Jusufbegovic', 'Adnan Muslija']
8
+ s.email = ['bakir@atlantbh.com', 'adnan.muslija@atlantbh.com']
9
+ s.files = ['lib/rspec2db.rb', 'bin/rspec2db', 'config/rspec2db.yml']
10
+ s.executables = ['rspec2db']
11
+ s.files = `git ls-files`.split("\n")
12
+ s.homepage = 'https://github.com/ATLANTBH/rspec'
13
+
14
+ s.add_dependency 'activerecord', '~>5.1'
15
+ s.add_dependency 'pg', '~>0.21'
16
+ s.add_dependency 'activerecord-postgresql-adapter'
17
+ s.add_dependency 'rspec', '>= 3.0'
18
+ end
data/spec/spec_spec.rb ADDED
@@ -0,0 +1,19 @@
1
+ describe "Dummy describe" do # this spec file is just for test / demo purposes
2
+ context "CoNtExT" do
3
+ it "does nothing, within context" do
4
+ 1.should eql(1)
5
+ end
6
+ end
7
+
8
+ it "does nothing again" do
9
+ 1.should eql(1)
10
+ end
11
+
12
+ it "raises" do
13
+ raise "oh no"
14
+ end
15
+
16
+ it "pends" do pending
17
+ end
18
+
19
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec2db
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Nermin Caluk
8
+ - Bakir Jusufbegovic
9
+ - Adnan Muslija
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2018-01-01 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '5.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '5.1'
29
+ - !ruby/object:Gem::Dependency
30
+ name: pg
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '0.21'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '0.21'
43
+ - !ruby/object:Gem::Dependency
44
+ name: activerecord-postgresql-adapter
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rspec
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '3.0'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.0'
71
+ description: A simple RSpec formatter to enable writing RSpec test results to any
72
+ database using ActiveRecord. Specify DB connection in a yml file and put path to
73
+ that file as --options PATH in your .rspec file
74
+ email:
75
+ - bakir@atlantbh.com
76
+ - adnan.muslija@atlantbh.com
77
+ executables:
78
+ - rspec2db
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - ".gitignore"
83
+ - ".project"
84
+ - ".rspec"
85
+ - Gemfile
86
+ - Gemfile.lock
87
+ - README.md
88
+ - bin/rspec2db
89
+ - config/rspec2db.yml
90
+ - config/rspec_db.rb
91
+ - lib/rspec2db.rb
92
+ - lib/rspec2db/db/build_execution_stats.rb
93
+ - lib/rspec2db/db/migrations/2_add_test_case_screenshots.rb
94
+ - lib/rspec2db/db/schema.rb
95
+ - lib/rspec2db/models/test_case.rb
96
+ - lib/rspec2db/models/test_run.rb
97
+ - lib/rspec2db/models/test_suite.rb
98
+ - lib/rspec2db/utils/db_utils.rb
99
+ - lib/rspec2db/utils/rspec_configuration_helper.rb
100
+ - rspec2db.gemspec
101
+ - spec/spec_spec.rb
102
+ homepage: https://github.com/ATLANTBH/rspec
103
+ licenses: []
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.8
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Save your RSpec test results to a database
125
+ test_files: []