swiftrail 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ module Swiftrail
2
+ module Junit
3
+ TestSuite = Struct.new(:suite_name, :test_cases)
4
+ TestCase = Struct.new(:full_class_name, :class_name, :test_name, :failures, :duration) do
5
+ def success?
6
+ failures.empty?
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,58 @@
1
+ require 'swiftrail/swift/test'
2
+
3
+ module Swiftrail
4
+ module Swift
5
+ class Parser
6
+ def initialize(test_patterns)
7
+ @test_patterns = test_patterns
8
+ end
9
+
10
+ def parse
11
+ all_files.map do |file_name|
12
+ collect_test_cases(file_name, IO.read(file_name))
13
+ end.flatten
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :test_patterns
19
+ RegexMatch = Struct.new(:case_ids, :class_type?, :name)
20
+
21
+ def all_files
22
+ test_patterns.map do |test_pattern|
23
+ Dir[test_pattern]
24
+ end.flatten.uniq
25
+ end
26
+
27
+ # @return [Test]
28
+ def collect_test_cases(file_name, content)
29
+ class_cases = []
30
+ class_name = ''
31
+ content.scan(cases_regex).map { |match|
32
+ RegexMatch.new(sanitize(match[1]), match[2] == 'class ', match[3])
33
+ }.reduce([]) { |result, match|
34
+ if match.class_type?
35
+ class_cases = match.case_ids
36
+ class_name = match.name
37
+ else
38
+ result << Test.new(file_name, class_name, match.name, (match.case_ids + class_cases).uniq)
39
+ end
40
+ result
41
+ }
42
+ end
43
+
44
+ def cases_regex
45
+ %r{(//\s*TESTRAIL\s*([(C(0-9)*)|\s|,]*)$\s*)*(func |class )\s*(\w*)\s*(\(\)|:\s*XCTestCase)}im
46
+ end
47
+
48
+ def sanitize(case_ids)
49
+ return [] if case_ids.nil? || case_ids.empty?
50
+
51
+ case_ids.delete(' ')
52
+ .split(',')
53
+ .select { |case_id| case_id.downcase.start_with?('c') }
54
+ .map { |case_id| case_id[1..-1] }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ module Swiftrail
2
+ module Swift
3
+ Test = Struct.new(:file_name, :class_name, :test_name, :case_ids) do
4
+ def to_json(*args)
5
+ {
6
+ file_name: file_name,
7
+ class_name: class_name,
8
+ test_name: test_name,
9
+ case_ids: case_ids
10
+ }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,82 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ require 'swiftrail/testrail/api/error'
6
+ require 'swiftrail/testrail/api/test_rail_test'
7
+
8
+ module Swiftrail
9
+ module Testrail
10
+ module Api
11
+ class Client
12
+ def initialize(base_url, username, password, run_id)
13
+ @conn = connection(URI.parse(base_url))
14
+ @run_id = run_id
15
+ @username = username
16
+ @password = password
17
+ end
18
+
19
+ def publish_results(results)
20
+ response = conn.request(post_request(publish_result_path, results))
21
+
22
+ raise Error::InvalidRequest if response.code == '400'
23
+ raise Error::NoPermission if response.code == '403'
24
+ raise Error::Unknown, response.code unless response.code == '200'
25
+
26
+ true
27
+ end
28
+
29
+ # @return [TestRailTest]
30
+ def all_tests
31
+ response = conn.request(get_request(tests_for_run_path))
32
+
33
+ raise Error::InvalidRequest if response.code == '400'
34
+ raise Error::NoPermission if response.code == '403'
35
+ raise Error::Unknown, response.code unless response.code == '200'
36
+
37
+ JSON.parse(response.body).map do |item|
38
+ TestRailTest.from_json(item)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :conn, :run_id, :username, :password
45
+
46
+ def connection(url)
47
+ conn = Net::HTTP.new(url.host, url.port)
48
+ conn.use_ssl = true
49
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
50
+ conn
51
+ end
52
+
53
+ def post_request(path, results)
54
+ request = Net::HTTP::Post.new(path)
55
+ request.body = JSON.dump(post_json(results))
56
+ request.basic_auth(username, password)
57
+ request.add_field('Content-Type', 'application/json')
58
+ request
59
+ end
60
+
61
+ def get_request(path)
62
+ request = Net::HTTP::Get.new(path)
63
+ request.basic_auth(username, password)
64
+ request.add_field('Content-Type', 'application/json')
65
+ request
66
+ end
67
+
68
+ def post_json(results)
69
+ { results: results.map(&:to_json) }
70
+ end
71
+
72
+ def publish_result_path
73
+ "/index.php?/api/v2/add_results_for_cases/#{run_id}"
74
+ end
75
+
76
+ def tests_for_run_path
77
+ "/index.php?/api/v2/get_tests/#{run_id}"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,27 @@
1
+ require 'swiftrail/errors/error'
2
+
3
+ module Swiftrail
4
+ module Testrail
5
+ module Api
6
+ module Error
7
+ class InvalidRequest < Errors::Base
8
+ def initialize
9
+ super('Invalid or unknown test run/cases')
10
+ end
11
+ end
12
+
13
+ class NoPermission < Errors::Base
14
+ def initialize
15
+ super('No permissions to add test results or no access to the project')
16
+ end
17
+ end
18
+
19
+ class Unknown < Errors::Base
20
+ def initialize(status_code)
21
+ super("There has been an unknown error.rb (status code: #{status_code} while doing api request")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module Swiftrail
2
+ module Testrail
3
+ module Api
4
+ # noinspection RubyConstantNamingConvention
5
+ TestCaseResult = Struct.new(:case_id, :comment, :duration, :status) do
6
+ def to_json(*_args)
7
+ {
8
+ case_id: case_id,
9
+ status_id: status,
10
+ comment: comment,
11
+ elapsed: duration
12
+ }
13
+ end
14
+
15
+ def self.from(case_id, results)
16
+ TestCaseResult.new(case_id, comment(results), duration(results), status(results))
17
+ end
18
+
19
+ def self.status(results)
20
+ results.map(&:success?).all? ? 1 : 5
21
+ end
22
+
23
+ def self.comment(results)
24
+ comments = results.map do |result|
25
+ value = " - #{result.success? ? '**SUCCESS**' : '**FAILURE**'}, `#{result.class_name}.#{result.test_name}` in `#{result.file_name}`"
26
+ value += " with following errors \n" + result.failures.map { |message| " + `#{message}`"}.join("\n") unless result.success?
27
+ value
28
+ end
29
+ (['#Results:#'] + comments + ['']).join("\n")
30
+ end
31
+
32
+ def self.duration(results)
33
+ format('%.2fs', results.map(&:duration).sum)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module Swiftrail
2
+ module Testrail
3
+ module Api
4
+ TestRailTest = Struct.new(:case_id, :id, :title) do
5
+ def self.from_json(json)
6
+ TestRailTest.new(json['case_id'], json['id'], json['title'])
7
+ end
8
+
9
+ def to_json(*_args)
10
+ {
11
+ id: id,
12
+ case_id: case_id,
13
+ title: title
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ require 'swiftrail/testrail/errors'
2
+ require 'swiftrail/testrail/api/test_case_result'
3
+ require 'swiftrail/testrail/intermediate_result'
4
+
5
+ module Swiftrail
6
+ module Testrail
7
+ class Assembler
8
+ def initialize(swift_tests, junit_test_suites)
9
+ @swift_tests = swift_tests
10
+ @junit_test_suites = junit_test_suites
11
+ end
12
+
13
+ def assemble
14
+ intermediate_results.group_by(&:case_id).map do |k, v|
15
+ Api::TestCaseResult.from(k, v)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :swift_tests, :junit_test_suites
22
+
23
+ def intermediate_results
24
+ junit_test_suites.map(&method(:test_cases)).flatten
25
+ end
26
+
27
+ def test_cases(test_suite)
28
+ test_suite.test_cases.map do |test_case|
29
+ swift_test = swift_test_for(test_case)
30
+ if swift_test.nil?
31
+ []
32
+ else
33
+ swift_test.case_ids.map do |case_id|
34
+ IntermediateResult.new(swift_test.file_name, swift_test.class_name, swift_test.test_name, test_case.success?, test_case.duration, test_case.failures, case_id)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def swift_test_for(junit_test_case)
41
+ tests = swift_tests.select do |test|
42
+ test.class_name == junit_test_case.class_name &&
43
+ test.test_name == junit_test_case.test_name
44
+ end
45
+ raise Error::Ambiguity(junit_test_case, tests) if tests.count > 1
46
+
47
+ tests.first
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,62 @@
1
+ require 'swiftrail/swift/parser'
2
+
3
+ module Swiftrail
4
+ module Testrail
5
+ class Coverage
6
+ def initialize(tests_patterns, test_rail_username, test_rail_password, test_rail_base_url)
7
+ @tests_patterns = tests_patterns
8
+ @test_rail_username = test_rail_username
9
+ @test_rail_password = test_rail_password
10
+ @test_rail_base_url = test_rail_base_url
11
+ end
12
+
13
+ def coverage_report(run_id)
14
+ coverage_results = test_rail_client(run_id).all_tests.each_with_object({}) do |test, hash|
15
+ hash[test] = swift_tests_for_case_id(test.case_id)
16
+ end
17
+ generate_report(coverage_results)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :tests_patterns, :test_rail_username, :test_rail_password, :test_rail_base_url
23
+
24
+ def swift_tests_for_case_id(case_id)
25
+ swift_tests.select do |swift_test|
26
+ swift_test.case_ids.include?(case_id.to_s)
27
+ end
28
+ end
29
+
30
+ def test_rail_client(run_id)
31
+ Swiftrail::Testrail::Api::Client.new(test_rail_base_url, test_rail_username, test_rail_password, run_id)
32
+ end
33
+
34
+ # @return json
35
+ def generate_report(results)
36
+ {
37
+ coverage: {
38
+ covered: results.values.reject { |value| value.nil? || value.empty? }.count.to_f,
39
+ total_test_cases: results.keys.count.to_f,
40
+ percentage: results.values.reject { |value| value.nil? || value.empty? }.count.to_f / results.keys.count.to_f
41
+ },
42
+ metadata: results.map do |test_rail_test, swift_tests|
43
+ {
44
+ test_rail: test_rail_test.to_json,
45
+ covered_by: [
46
+ swift_tests.map(&:to_json)
47
+ ]
48
+ }
49
+ end
50
+ }
51
+ end
52
+
53
+ def swift_tests
54
+ @swift_tests ||= swift_test_parser.parse
55
+ end
56
+
57
+ def swift_test_parser
58
+ Swiftrail::Swift::Parser.new(tests_patterns)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ require 'swiftrail/errors/error'
2
+
3
+ module Swiftrail
4
+ module Testrail
5
+ module Error
6
+ class Ambiguity < Errors::Base
7
+ def initialize(test_case, swift_tests)
8
+ super("Test Case (#{test_case} from junit report, corresponds to multiple swift tests #{swift_tests}")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Swiftrail
2
+ module Testrail
3
+ IntermediateResult = Struct.new(:file_name, :class_name, :test_name, :success?, :duration, :failures, :case_id)
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ require 'swiftrail/swift/parser'
2
+
3
+ module Swiftrail
4
+ module Testrail
5
+ class Lint
6
+ def initialize(tests_patterns, test_rail_username, test_rail_password, test_rail_base_url)
7
+ @tests_patterns = tests_patterns
8
+ @test_rail_username = test_rail_username
9
+ @test_rail_password = test_rail_password
10
+ @test_rail_base_url = test_rail_base_url
11
+ end
12
+
13
+ def lint_report(run_id)
14
+ missing_cases = all_tests_by_case_id.keys - test_rail_client(run_id).all_tests.map(&:case_id).map(&:to_s)
15
+ generate_report(missing_cases)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :tests_patterns, :test_rail_username, :test_rail_password, :test_rail_base_url
21
+
22
+ def swift_tests_for_case_id(case_id)
23
+ swift_tests.select do |swift_test|
24
+ swift_test.case_ids.include?(case_id.to_s)
25
+ end
26
+ end
27
+
28
+ def test_rail_client(run_id)
29
+ Swiftrail::Testrail::Api::Client.new(test_rail_base_url, test_rail_username, test_rail_password, run_id)
30
+ end
31
+
32
+ def generate_report(case_ids)
33
+ {
34
+ reporting_invalid_case_ids: case_ids.map do |case_id|
35
+ { case_id => all_tests_by_case_id[case_id].map(&:to_json) }
36
+ end
37
+ }
38
+ end
39
+
40
+ def all_tests_by_case_id
41
+ @all_tests_by_case_id ||= swift_tests.each_with_object({}) do |test, hash|
42
+ test.case_ids.map do |case_id|
43
+ if hash.key?(case_id)
44
+ hash[case_id] << test
45
+ else
46
+ hash[case_id] = [test]
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def swift_tests
53
+ swift_test_parser.parse
54
+ end
55
+
56
+ def swift_test_parser
57
+ Swiftrail::Swift::Parser.new(tests_patterns)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,42 @@
1
+ require 'swiftrail/junit/parser'
2
+ require 'swiftrail/swift/parser'
3
+ require 'swiftrail/testrail/assembler'
4
+ require 'swiftrail/testrail/api/client'
5
+
6
+ module Swiftrail
7
+ module Testrail
8
+ class Reporter
9
+ def initialize(test_report_patterns, tests_patterns, test_rail_username, test_rail_password, test_rail_base_url)
10
+ @test_report_patterns = test_report_patterns
11
+ @tests_patterns = tests_patterns
12
+ @test_rail_username = test_rail_username
13
+ @test_rail_password = test_rail_password
14
+ @test_rail_base_url = test_rail_base_url
15
+ end
16
+
17
+ def report_results(run_id)
18
+ test_rail_client(run_id).publish_results(assembler(swift_test_parser.parse, junit_parser.parse).assemble)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :test_report_patterns, :tests_patterns, :test_rail_username, :test_rail_password, :test_rail_base_url
24
+
25
+ def test_rail_client(run_id)
26
+ Api::Client.new(test_rail_base_url, test_rail_username, test_rail_password, run_id)
27
+ end
28
+
29
+ def assembler(swift_tests, junit_test_suites)
30
+ Assembler.new(swift_tests, junit_test_suites)
31
+ end
32
+
33
+ def swift_test_parser
34
+ Swiftrail::Swift::Parser.new(tests_patterns)
35
+ end
36
+
37
+ def junit_parser
38
+ Swiftrail::Junit::Parser.new(test_report_patterns)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Swiftrail
2
+ VERSION = '0.1.2'.freeze
3
+ end
data/lib/swiftrail.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'swiftrail/version'
2
+
3
+ require 'swiftrail/junit/parser'
4
+ require 'swiftrail/swift/parser'
5
+ require 'swiftrail/testrail/assembler'
6
+ require 'swiftrail/testrail/reporter'
7
+ require 'swiftrail/testrail/coverage'
8
+ require 'swiftrail/testrail/lint'
9
+ require 'swiftrail/testrail/api/client'
data/swiftrail.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'swiftrail/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'swiftrail'
7
+ spec.version = Swiftrail::VERSION
8
+ spec.authors = ['Slavko Krucaj']
9
+ spec.email = ['slavko.krucaj@gmail.com']
10
+
11
+ spec.summary = 'Library that manages reporting of swift test results to test rail.'
12
+ spec.homepage = 'https://www.github.com/SlavkoKrucaj/swiftrail'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
16
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ end
18
+ spec.bindir = 'bin'
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'nokogiri', '~> 1.10'
23
+ spec.add_dependency 'thor', '~> 0.20'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 2.0'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swiftrail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Slavko Krucaj
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :runtime
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: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.20'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description:
84
+ email:
85
+ - slavko.krucaj@gmail.com
86
+ executables:
87
+ - swiftrail
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".rubocop.yml"
94
+ - ".ruby-version"
95
+ - ".travis.yml"
96
+ - CODE_OF_CONDUCT.md
97
+ - Gemfile
98
+ - Gemfile.lock
99
+ - LICENSE.txt
100
+ - README.md
101
+ - Rakefile
102
+ - bin/swiftrail
103
+ - lib/swiftrail.rb
104
+ - lib/swiftrail/cli.rb
105
+ - lib/swiftrail/config/errors.rb
106
+ - lib/swiftrail/config/reader.rb
107
+ - lib/swiftrail/errors/error.rb
108
+ - lib/swiftrail/junit/parser.rb
109
+ - lib/swiftrail/junit/report.rb
110
+ - lib/swiftrail/swift/parser.rb
111
+ - lib/swiftrail/swift/test.rb
112
+ - lib/swiftrail/testrail/api/client.rb
113
+ - lib/swiftrail/testrail/api/error.rb
114
+ - lib/swiftrail/testrail/api/test_case_result.rb
115
+ - lib/swiftrail/testrail/api/test_rail_test.rb
116
+ - lib/swiftrail/testrail/assembler.rb
117
+ - lib/swiftrail/testrail/coverage.rb
118
+ - lib/swiftrail/testrail/errors.rb
119
+ - lib/swiftrail/testrail/intermediate_result.rb
120
+ - lib/swiftrail/testrail/lint.rb
121
+ - lib/swiftrail/testrail/reporter.rb
122
+ - lib/swiftrail/version.rb
123
+ - swiftrail.gemspec
124
+ homepage: https://www.github.com/SlavkoKrucaj/swiftrail
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.0.1
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Library that manages reporting of swift test results to test rail.
147
+ test_files: []