korinthenkacker 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1c174ae40b2ddbc7d73ca1bce0d2e97e630362d
4
- data.tar.gz: 7dda75b32aa473799d35ed4a901ab282ab2cc6d1
3
+ metadata.gz: c8b03b372dfb50d7134c8f789c824613e22ab5fd
4
+ data.tar.gz: 8613ab7127a2595676f1f6d25723f25f385518dc
5
5
  SHA512:
6
- metadata.gz: 5e2d9147896eb71cd1f171529251c1392207f36cbc5a474d1553ae3c23a735c5bd0395f26e683e75ab0a3709e92ba230b27d463fb71b85787caaa18d64e27529
7
- data.tar.gz: 8a0f648765d04537361468847211d5a439c84f7edcd2d010193dd1b89850f29e6de8db9c56d946f51c9f2fac12951aadd88772c520c9bdf634cbd5219322d26c
6
+ metadata.gz: 3c1957868677c50eb69b66c5945fe941adbcf6dedecff57609ed15460e7c25386e3e3a3d580304da2a434e413eeb5cbaaf6fa4542c4e31e65a2affd00daefcba
7
+ data.tar.gz: 2bfcb6c65bd58565879b52cb724a581f60a5b8bada1402ef539b3cf5fe2a71a4bb2b225450087749ad3fd1b516d0863630460aca85e6d9582809c14a4e205dd4
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/README.md CHANGED
@@ -4,6 +4,10 @@ Know your Jenkins
4
4
 
5
5
  ## Setup
6
6
 
7
+ ```
8
+ gem install korinthenkacker
9
+ ```
10
+
7
11
  Add a `.korinthenkacker.yml` containing the jenkins url:
8
12
 
9
13
  ```
@@ -60,6 +64,14 @@ $> korinthenkacker failed_scenarios soundcloud_rac_acceptance_tests_0 47
60
64
  47 80.93447 When a follow request fails, display unfollowed state
61
65
  ```
62
66
 
67
+ ```
68
+ $> korinthenkacker flaky_scenarios soundcloud_rac_acceptance_tests -l 20
69
+ # duration scenario name
70
+ 228 63.409763 'Player.Foreground playback with server errors'
71
+ 225 3.24821 'Player shortcut (Mini waveform).Mini waveform stops animating on track error'
72
+ 223 30.674717 'Player Audio Ads Playback.Going back after playing ad does not show ad again'
73
+ ```
74
+
63
75
  ```
64
76
  $> korinthenkacker report soundcloud_rac_acceptance_tests_{0,1,2,3,4} -l 5
65
77
  soundcloud_rac_acceptance_tests_0
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler/gem_tasks'
2
4
 
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.6'
24
24
  spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
25
26
  end
@@ -3,79 +3,84 @@ require 'thor'
3
3
  require_relative 'api'
4
4
  require_relative 'configuration'
5
5
  require_relative 'test_report'
6
+ require_relative 'reporters/verbose'
7
+ require_relative 'reporters/simple'
6
8
  require_relative 'job_info'
9
+ require_relative 'filters/failed'
10
+ require_relative 'filters/flaky'
7
11
 
8
12
  module Korinthenkacker
9
13
  class CLI < Thor
10
14
  package_name 'korinthenkacker'
11
15
 
12
16
  desc 'jobs', 'Show all jobs'
17
+ method_option :simple, aliases: '-s', type: :boolean, desc: 'simple output formatting'
13
18
  def jobs
14
- puts api.jobs["jobs"].map{ |job| job['name'] }
19
+ jobs = api.jobs['jobs'].map { |job| JobInfo.new(job) }
20
+ reporter(options.simple).print_jobs_report(jobs)
15
21
  end
16
22
 
17
23
  desc 'builds JOBNAME', 'Display JOBNAME builds and their status'
18
- method_option :limit, :aliases => '-l', :type => :numeric
24
+ method_option :limit, aliases: '-l', type: :numeric
25
+ method_option :simple, aliases: '-s', type: :boolean, desc: 'simple output formatting'
19
26
  def builds(jobname)
20
27
  reports = test_reports(jobname, options.limit)
21
- output = []
22
- output << "#\tsuccess\tduration"
23
- reports.each{ |report|
24
- output << "#{report.build}\t#{report.success?}\t#{report.duration}"
25
- }
26
- puts output.join("\n")
28
+ reporter(options.simple).print_builds_report(reports)
27
29
  end
28
30
 
29
31
  desc 'failed_scenarios JOBNAME [BUILD]', 'Display JOBNAME failing scenarios'
30
- method_option :limit, :aliases => '-l', :type => :numeric
32
+ method_option :limit, aliases: '-l', type: :numeric
33
+ method_option :simple, aliases: '-s', type: :boolean, desc: 'simple output formatting'
31
34
  def failed_scenarios(jobname, build=nil)
32
- if build
33
- test_report = TestReport.new(jobname, build, api.test_report(jobname, build))
34
- print_failed_table_header unless test_report.failed_cases.empty?
35
- test_report.failed_cases.each { |failed_case|
36
- print_scenario_report(build, failed_case)
37
- }
38
- else
39
- reports = test_reports(jobname, options.limit)
40
- failed_build_reports = reports.select{ |report| !report.success? }
41
- print_failed_table_header unless failed_build_reports.empty?
42
- failed_build_reports.each{ |report|
43
- report.failed_cases.each{ |failed_case|
44
- print_scenario_report(report.build, failed_case)
45
- }
46
- }
47
- end
35
+ failed_cases = failed_cases_for(jobname, build)
36
+ reporter(options.simple).print_scenario_reports(failed_cases)
37
+ end
38
+
39
+ desc 'flaky_scenarios JOBNAME', 'Display JOBNAME flaky scenarios'
40
+ method_option :limit, aliases: '-l', type: :numeric
41
+ method_option :simple, aliases: '-s', type: :boolean, desc: 'simple output formatting'
42
+ def flaky_scenarios(jobname)
43
+ failed_cases = failed_cases_for(jobname)
44
+ flaky_cases = flaky_filter.filter(failed_cases)
45
+ reporter(options.simple).print_scenario_reports(flaky_cases)
48
46
  end
49
47
 
50
48
  desc 'report [JOBS...]', 'Display failing scenario for jobs'
51
- method_option :limit, :aliases => '-l', :type => :numeric
49
+ method_option :limit, aliases: '-l', type: :numeric
52
50
  def report(*jobs)
53
- jobs.each{ |job|
51
+ jobs.each do |job|
54
52
  puts job
55
53
  failed_scenarios(job, nil)
56
- }
54
+ end
57
55
  end
58
56
 
59
57
  private
60
58
 
61
- def print_failed_table_header
62
- printf("%s\t%-15s\t%s\n", '#', 'duration', 'failed scenario name')
59
+ def failed_cases_for(jobname, build=nil)
60
+ test_reports = build.nil? ? test_reports(jobname, options.limit) : [test_report(jobname, build)]
61
+ failed_filter.filter(test_reports.map(&:cases).flatten)
63
62
  end
64
63
 
65
- def print_scenario_report(build, scenario)
66
- printf("%s\t%-15s\t'%s'\n", build, scenario.duration, scenario.name)
64
+ def test_report(jobname, build)
65
+ TestReport.new(jobname, build, api.test_report(jobname, build))
67
66
  end
68
67
 
69
68
  def test_reports(jobname, limit=nil)
70
69
  job_info = JobInfo.new(api.job(jobname))
71
- builds = options.limit ? job_info.build_numbers.first(options.limit) : job_info.build_numbers
72
- builds.map{ |build|
73
- begin
74
- TestReport.new(jobname, build, api.test_report(jobname, build))
75
- rescue Exception => ex
76
- nil
77
- end
78
- }.compact
70
+ builds = limit ? job_info.build_numbers.first(limit) : job_info.build_numbers
71
+ builds.map { |build| test_report(jobname, build) rescue nil }.compact # TODO: proper catching
72
+ end
73
+
74
+ def reporter(simple=false)
75
+ @reporter ||= simple ? Reporters::Simple.new : Reporters::Verbose.new
76
+ end
77
+
78
+ def failed_filter
79
+ @failed_filter ||= Filters::Failed.new
80
+ end
81
+
82
+ def flaky_filter
83
+ @flaky_filter ||= Filters::Flaky.new
79
84
  end
80
85
 
81
86
  def api
@@ -0,0 +1,9 @@
1
+ module Korinthenkacker
2
+ module Filters
3
+ class Failed
4
+ def filter(cases)
5
+ cases.select(&:failure?)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Korinthenkacker
2
+ module Filters
3
+ class Flaky
4
+ def filter(cases)
5
+ unique_cases = Set.new(cases)
6
+ unique_cases.select do |unique_case|
7
+ cases.any? { |other_case| unique_case.sibling?(other_case) }
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -9,7 +9,10 @@ module Korinthenkacker
9
9
  end
10
10
 
11
11
  def build_numbers
12
- @json['builds'].map{ |build| build['number'] }
12
+ json['builds'].map { |build| build['number'] }
13
13
  end
14
+
15
+ private
16
+ attr_reader :json
14
17
  end
15
18
  end
@@ -0,0 +1,25 @@
1
+ module Korinthenkacker
2
+ module Reporters
3
+ class Simple
4
+ def print_jobs_report(jobs)
5
+ puts jobs.map(&:name)
6
+ end
7
+
8
+ def print_builds_report(reports)
9
+ reports.map do |report|
10
+ "#{report.build}\t#{report.success?}\t#{report.duration}"
11
+ end.join("\n")
12
+ end
13
+
14
+ def print_scenario_reports(test_cases)
15
+ test_cases.each { |test_case| print_scenario_report(test_case) }
16
+ end
17
+
18
+ private
19
+
20
+ def print_scenario_report(scenario)
21
+ printf("%s\t%s\t'%s'\n", scenario.build, scenario.duration, scenario.full_name)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ module Korinthenkacker
2
+ module Reporters
3
+ class Verbose
4
+ def print_jobs_report(jobs)
5
+ puts jobs.map(&:name)
6
+ end
7
+
8
+ def print_builds_report(reports)
9
+ output = ["#\tsuccess\tduration"]
10
+ reports.each do |report|
11
+ output << "#{report.build}\t#{report.success?}\t#{report.duration}"
12
+ end
13
+ puts output.join("\n")
14
+ end
15
+
16
+ def print_scenario_reports(test_cases)
17
+ print_table_header unless test_cases.empty?
18
+
19
+ test_cases.each { |test_case| print_scenario_report(test_case) }
20
+ end
21
+
22
+ private
23
+
24
+ def print_table_header
25
+ printf("%s\t%-15s\t%s\n", '#', 'duration', 'scenario name')
26
+ end
27
+
28
+ def print_scenario_report(scenario)
29
+ printf("%s\t%-15s\t'%s'\n", scenario.build, scenario.duration, scenario.full_name)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,18 +1,42 @@
1
1
  module Korinthenkacker
2
2
  class TestCase
3
- attr_reader :name, :duration, :failed_since, :status
3
+ attr_reader :name, :class_name, :duration, :failed_since, :status, :build
4
4
 
5
- def initialize(json)
5
+ def initialize(json, build)
6
6
  @name = json['name']
7
+ @class_name = json['className']
7
8
  @duration = json['duration']
8
9
  @failed_since = json['duration']
9
10
  @status = json['status']
11
+ @failed_since = json['failedSince']
12
+ @build = build
13
+ end
14
+
15
+ def full_name
16
+ [class_name, name].join('. ')
10
17
  end
11
18
 
12
19
  def success?
13
20
  !(status == 'REGRESSION' || status == 'FAILED')
14
21
  end
15
22
 
23
+ def failure?
24
+ !success?
25
+ end
26
+
27
+ def sibling?(other)
28
+ other == self && other.failed_since != self.failed_since
29
+ end
30
+
31
+ def ==(other)
32
+ other.class == self.class && other.full_name == self.full_name
33
+ end
34
+ alias_method :eql?, :==
35
+
36
+ def hash
37
+ full_name.hash
38
+ end
39
+
16
40
  private
17
41
 
18
42
  def status_with_json(json)
@@ -2,7 +2,7 @@ require_relative 'test_case'
2
2
 
3
3
  module Korinthenkacker
4
4
  class TestReport
5
- attr_reader :jobname, :build
5
+ attr_reader :jobname, :build, :json
6
6
 
7
7
  def initialize(jobname, build, json)
8
8
  @jobname = jobname
@@ -11,36 +11,34 @@ module Korinthenkacker
11
11
  end
12
12
 
13
13
  def duration
14
- @json['duration']
14
+ json['duration']
15
15
  end
16
16
 
17
17
  def fail_count
18
- @json['failCount']
18
+ json['failCount']
19
19
  end
20
20
 
21
21
  def pass_count
22
- @json['passCount']
22
+ json['passCount']
23
23
  end
24
24
 
25
25
  def skip_count
26
- @json['skipCount']
26
+ json['skipCount']
27
27
  end
28
28
 
29
29
  def success?
30
30
  fail_count == 0 && skip_count == 0
31
31
  end
32
32
 
33
- def failed_cases
34
- all_cases.select { |test_case| !test_case.success? }
33
+ def failure?
34
+ !success?
35
35
  end
36
36
 
37
- private
38
-
39
- def all_cases
40
- @json['suites']
37
+ def cases
38
+ json['suites']
41
39
  .map { |suite| suite['cases'] }
42
40
  .flatten
43
- .map { |my_case| TestCase.new(my_case) }
41
+ .map { |my_case| TestCase.new(my_case, build) }
44
42
  end
45
43
  end
46
44
  end
@@ -1,3 +1,3 @@
1
1
  module Korinthenkacker
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+ require 'korinthenkacker/filters/failed'
3
+ require 'korinthenkacker/test_case'
4
+
5
+ module Korinthenkacker
6
+ module Filters
7
+ describe Failed do
8
+ describe '#filter' do
9
+ let(:report_json_path) { File.expand_path('../../fixtures/test_cases.json', __FILE__) }
10
+ let(:report_json_fixture) { JSON.parse(File.read(report_json_path)) }
11
+ let(:test_cases) { report_json_fixture.map { |json_case| TestCase.new(json_case, 1) } }
12
+
13
+ context 'with empty TestCases' do
14
+ let(:test_cases) { [] }
15
+
16
+ it 'returns and empty array' do
17
+ expect(subject.filter([])).to be_empty
18
+ end
19
+ end
20
+
21
+ context 'with non-empty cases' do
22
+ it 'returns the correct amount of TestCases' do
23
+ expect(subject.filter(test_cases).count).to eql(8)
24
+ end
25
+
26
+ it 'returns only failed TestCases' do
27
+ expect(subject.filter(test_cases).all?(&:failure?)).to be_truthy
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+ require 'korinthenkacker/filters/flaky'
3
+ require 'korinthenkacker/test_case'
4
+
5
+ module Korinthenkacker
6
+ module Filters
7
+ describe Flaky do
8
+ let(:json_fixture_path) { File.expand_path('../../fixtures/test_cases.json', __FILE__) }
9
+ let(:json_fixture) { JSON.parse(File.read(json_fixture_path)) }
10
+ let(:test_cases) { json_fixture.map { |json_case| TestCase.new(json_case, 1) } }
11
+
12
+ context 'with empty TestCases' do
13
+ let(:test_cases) { [] }
14
+
15
+ it 'returns and empty array' do
16
+ expect(subject.filter([])).to be_empty
17
+ end
18
+ end
19
+
20
+ context 'with non-empty cases' do
21
+ it 'returns the correct number of flaky TestCases' do
22
+ expect(subject.filter(test_cases).count).to eql(2)
23
+ end
24
+
25
+ it 'returns only the flaky TestCases' do
26
+ flaky_test_case_names = subject.filter(test_cases).map(&:class_name)
27
+ expect(flaky_test_case_names).to include('Flaky Spec A')
28
+ expect(flaky_test_case_names).to include('Flaky Spec B')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,170 @@
1
+ [
2
+ {
3
+ "age": 1,
4
+ "className": "Player",
5
+ "duration": 63.409763,
6
+ "errorDetails": "failed Foreground playback with server errors",
7
+ "errorStackTrace": "\n Scenario: Foreground playback with server errors\n\nGiven explore and player and waveform datasets are loaded\nGiven that the app is started\nAnd that \"Media\" endpoint returns status code 404\nAnd I go to \"Trending Music Track 1\" player_page\nThen I am in error state\n\nMessage:\n\n wait_for_page_animations_to_finish (elapsed time: 15.000691, time 2014-09-25 00:41:27 +0200) (RuntimeError)\n./features/lib/helpers/wait_until.rb:28:in `rescue in wait_until'\n./features/lib/helpers/wait_until.rb:15:in `wait_until'\n./features/lib/helpers/waiter.rb:154:in `wait_for_animations'\n./features/lib/helpers/waiter.rb:94:in `wait_for_page_animations_to_finish'\n./features/step_definitions/player_page_steps.rb:175:in `/^I am in error state$/'\nfeatures/acceptance/player/player.feature:56:in `Then I am in error state'\n ",
8
+ "failedSince": 228,
9
+ "name": "Foreground playback with server errors",
10
+ "skipped": false,
11
+ "skippedMessage": null,
12
+ "status": "REGRESSION",
13
+ "stderr": "",
14
+ "stdout": ""
15
+ },
16
+ {
17
+ "age": 0,
18
+ "className": "Playlist deeplinks",
19
+ "duration": 31.45592,
20
+ "errorDetails": null,
21
+ "errorStackTrace": null,
22
+ "failedSince": 0,
23
+ "name": "Splash screen is visible while loading",
24
+ "skipped": false,
25
+ "skippedMessage": null,
26
+ "status": "PASSED",
27
+ "stderr": "",
28
+ "stdout": ""
29
+ },
30
+ {
31
+ "age": 1,
32
+ "className": "Liking a track in player",
33
+ "duration": 86.92972,
34
+ "errorDetails": "failed Liking an unliked track",
35
+ "errorStackTrace": "\n Scenario: Liking an unliked track\n\nGiven explore and player and signin and waveform and stream datasets are loaded\nAnd that the app is started\nGiven I am signed in\nAnd I am on feed_picker_page\nAnd I tap on \"Trending Music\" stream_cell\nAnd track_list loaded items\nAnd I go to \"Trending Music Track 1\" player_page\nAnd I tap the player\nWhen I tap the player like button\nThen after a while I should see the player unlike button\n\nMessage:\n\n couldn't open player from feed (RuntimeError)\n./features/lib/page/explore.rb:38:in `block in touch_track_with_title_and_wait_for_player'\n./features/lib/page/explore.rb:57:in `call'\n./features/lib/page/explore.rb:57:in `track_with_title'\n./features/lib/page/explore.rb:24:in `touch_track_with_title_and_wait_for_player'\n./features/step_definitions/explore_page_steps.rb:54:in `/^I tap on track titled \"(.*)\"$/'\n./features/step_definitions/player_page_steps.rb:4:in `/^I go to \"(.*)\" player_page$/'\nfeatures/acceptance/player/player_like.feature:42:in `And I go to \"Trending Music Track 1\" player_page'\n ",
36
+ "failedSince": 225,
37
+ "name": "Liking an unliked track",
38
+ "skipped": false,
39
+ "skippedMessage": null,
40
+ "status": "FAILED",
41
+ "stderr": "",
42
+ "stdout": ""
43
+ },
44
+ {
45
+ "age": 1,
46
+ "className": "Flaky Spec A",
47
+ "duration": 3.24821,
48
+ "errorDetails": "failed Mini waveform stops animating on track error",
49
+ "errorStackTrace": "\n Scenario: Mini waveform stops animating on track error\n\nGiven explore and player and waveform datasets are loaded\nAnd that the app is started\nAnd I am on explore_page\nAnd track_list loaded items\nWhen I go to \"Trending Music Track 1\" player_page\nThen I am in play state\nGiven that the network is unreachable\nThen offline_bar should be in unreachable state\nAnd I tap the next_player_page_button until I'm on \"Trending Music Track 3\" player_page\nThen I am in error state\nWhen I close the player\nThen mini waveform view should not be animating\n\nMessage:\n\n expected visible? to return true, got false (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/player_page_steps.rb:192:in `/^I should see the player error retry button$/'\n./features/step_definitions/player_page_steps.rb:177:in `/^I am in error state$/'\nfeatures/acceptance/player/player_shortcut.feature:43:in `Then I am in error state'\n ",
50
+ "failedSince": 225,
51
+ "name": "Mini waveform stops animating on track error",
52
+ "skipped": false,
53
+ "skippedMessage": null,
54
+ "status": "REGRESSION",
55
+ "stderr": "",
56
+ "stdout": ""
57
+ },
58
+ {
59
+ "age": 1,
60
+ "className": "Flaky Spec B",
61
+ "duration": 30.674717,
62
+ "errorDetails": "failed Going back after playing ad does not show ad again",
63
+ "errorStackTrace": "\n Scenario: Going back after playing ad does not show ad again\n\nGiven explore and player and waveform and audio_ad datasets are loaded\nAnd that the app is started\nAnd I am on explore_page\nGiven I go to \"Trending Music Track 1\" player_page\nAnd audio ad has loaded after \"Trending Music Track 1\"\nWhen I tap the next_player_page_button\nThen I should be on audio ad page\nAnd I should hear the audio ad \"MONEYZZZZ\"\nGiven I really want to wait for 4 seconds\nWhen I tap the next_player_page_button\nThen I should be on \"Trending Music Track 2\" player_page\nAnd I should hear music for track \"Trending Music Track 2\"\nAnd I should see the leave_behind\nWhen I tap the leave_behind close button\nThen I should not see the leave_behind\nWhen I tap the previous_player_page_button\nThen I should be on \"Trending Music Track 1\" player_page\n\nMessage:\n\n timed out waiting: timeout = 15, poll_sleep = 0.5 (elapsed time: 15.001166, time 2014-09-24 21:18:19 +0200) (RuntimeError)\n./features/lib/helpers/wait_until.rb:28:in `rescue in wait_until'\n./features/lib/helpers/wait_until.rb:15:in `wait_until'\n./features/step_definitions/player_page_steps.rb:63:in `/^I should be on \"(.*)\" player_page$/'\nfeatures/acceptance/player/player_audio_ad_playback.feature:52:in `Then I should be on \"Trending Music Track 1\" player_page'\n ",
64
+ "failedSince": 223,
65
+ "name": "Going back after playing ad does not show ad again",
66
+ "skipped": false,
67
+ "skippedMessage": null,
68
+ "status": "REGRESSION",
69
+ "stderr": "",
70
+ "stdout": ""
71
+ },
72
+ {
73
+ "age": 0,
74
+ "className": "Playlist deeplinks",
75
+ "duration": 32.77258,
76
+ "errorDetails": null,
77
+ "errorStackTrace": null,
78
+ "failedSince": 0,
79
+ "name": "Opening a valid playlist url (outline example : | soundcloud:playlists:1 |)",
80
+ "skipped": false,
81
+ "skippedMessage": null,
82
+ "status": "PASSED",
83
+ "stderr": "",
84
+ "stdout": ""
85
+ },
86
+ {
87
+ "age": 0,
88
+ "className": "Playlist deeplinks",
89
+ "duration": 32.77258,
90
+ "errorDetails": null,
91
+ "errorStackTrace": null,
92
+ "failedSince": 0,
93
+ "name": "Opening a valid playlist url (outline example : | soundcloud:playlists:1 |)",
94
+ "skipped": false,
95
+ "skippedMessage": null,
96
+ "status": "PASSED",
97
+ "stderr": "",
98
+ "stdout": ""
99
+ },
100
+ {
101
+ "age": 1,
102
+ "className": "Liking a track in player",
103
+ "duration": 86.88021,
104
+ "errorDetails": "failed When a user is offline and likes a track, display liked state and sync when online again",
105
+ "errorStackTrace": "\n Scenario: When a user is offline and likes a track, display liked state and sync when online again\n\nGiven explore and player and signin and waveform and stream datasets are loaded\nAnd that the app is started\nGiven I am signed in\nAnd I am on feed_picker_page\nAnd I tap on \"Trending Music\" stream_cell\nAnd track_list loaded items\nAnd I go to \"Trending Music Track 1\" player_page\nAnd I tap the player\nAnd that the network is unreachable\nThen offline_bar should be in unreachable state\nWhen I tap the player like button\nThen I should see the unlike button on player\nWhen that the network is reachable\n\nMessage:\n\n couldn't open player from feed (RuntimeError)\n./features/lib/page/explore.rb:38:in `block in touch_track_with_title_and_wait_for_player'\n./features/lib/page/explore.rb:57:in `call'\n./features/lib/page/explore.rb:57:in `track_with_title'\n./features/lib/page/explore.rb:24:in `touch_track_with_title_and_wait_for_player'\n./features/step_definitions/explore_page_steps.rb:54:in `/^I tap on track titled \"(.*)\"$/'\n./features/step_definitions/player_page_steps.rb:4:in `/^I go to \"(.*)\" player_page$/'\nfeatures/acceptance/player/player_like.feature:66:in `And I go to \"Trending Music Track 1\" player_page'\n ",
106
+ "failedSince": 223,
107
+ "name": "When a user is offline and likes a track, display liked state and sync when online again",
108
+ "skipped": false,
109
+ "skippedMessage": null,
110
+ "status": "REGRESSION",
111
+ "stderr": "",
112
+ "stdout": ""
113
+ },
114
+ {
115
+ "age": 7,
116
+ "className": "Search",
117
+ "duration": 26.78047,
118
+ "errorDetails": "failed ErrorView should not be shown when clearing search results",
119
+ "errorStackTrace": "\n Scenario: ErrorView should not be shown when clearing search results\n\nGiven explore and signin and search datasets are loaded\nAnd that the app is started\nAnd I am on search_page\nGiven that \"Search 0\" endpoint returns status code 404\nWhen I search for \"skrillex rulez\"\nThen I should not see any results\nAnd I should see the server_error_view\nAnd I should not see header_logo_view\nGiven search dataset is loaded\nWhen I clear the search box\nWhen I search for \"skrillex rulez\"\nAnd search results are loaded\nWhen I clear the search box\nThen I should not see the server_error_view\nAnd I should not see header_logo_view\n\nMessage:\n\n Error View is visible but it should not be (elapsed time: 15.001035, time 2014-09-24 21:21:04 +0200) (RuntimeError)\n./features/lib/helpers/wait_until.rb:28:in `rescue in wait_until'\n./features/lib/helpers/wait_until.rb:15:in `wait_until'\n./features/lib/helpers/waiter.rb:148:in `perform_until'\n./features/lib/helpers/waiter.rb:144:in `perform_until_after_delay'\n./features/step_definitions/search_page_steps.rb:78:in `/^I should not see the server_error_view$/'\nfeatures/acceptance/search/search.feature:121:in `Then I should not see the server_error_view'\n ",
120
+ "failedSince": 217,
121
+ "name": "ErrorView should not be shown when clearing search results",
122
+ "skipped": false,
123
+ "skippedMessage": null,
124
+ "status": "FAILED",
125
+ "stderr": "",
126
+ "stdout": ""
127
+ },
128
+ {
129
+ "age": 2,
130
+ "className": "Flaky Spec A",
131
+ "duration": 2.83868,
132
+ "errorDetails": "failed Mini waveform stops animating on track error",
133
+ "errorStackTrace": "\n Scenario: Mini waveform stops animating on track error\n\nGiven explore and player and waveform datasets are loaded\nAnd that the app is started\nAnd I am on explore_page\nAnd track_list loaded items\nWhen I go to \"Trending Music Track 1\" player_page\nThen I am in play state\nGiven that the network is unreachable\nThen offline_bar should be in unreachable state\nAnd I tap the next_player_page_button until I'm on \"Trending Music Track 3\" player_page\nThen I am in error state\nWhen I close the player\nThen mini waveform view should not be animating\n\nMessage:\n\n expected visible? to return true, got false (RSpec::Expectations::ExpectationNotMetError)\n./features/step_definitions/player_page_steps.rb:192:in `/^I should see the player error retry button$/'\n./features/step_definitions/player_page_steps.rb:177:in `/^I am in error state$/'\nfeatures/acceptance/player/player_shortcut.feature:43:in `Then I am in error state'\n ",
134
+ "failedSince": 221,
135
+ "name": "Mini waveform stops animating on track error",
136
+ "skipped": false,
137
+ "skippedMessage": null,
138
+ "status": "FAILED",
139
+ "stderr": "",
140
+ "stdout": ""
141
+ },
142
+ {
143
+ "age": 6,
144
+ "className": "Search",
145
+ "duration": 26.590137,
146
+ "errorDetails": "failed ErrorView should not be shown when clearing search results",
147
+ "errorStackTrace": "\n Scenario: ErrorView should not be shown when clearing search results\n\nGiven explore and signin and search datasets are loaded\nAnd that the app is started\nAnd I am on search_page\nGiven that \"Search 0\" endpoint returns status code 404\nWhen I search for \"skrillex rulez\"\nThen I should not see any results\nAnd I should see the server_error_view\nAnd I should not see header_logo_view\nGiven search dataset is loaded\nWhen I clear the search box\nWhen I search for \"skrillex rulez\"\nAnd search results are loaded\nWhen I clear the search box\nThen I should not see the server_error_view\nAnd I should not see header_logo_view\n\nMessage:\n\n Error View is visible but it should not be (elapsed time: 15.001807, time 2014-09-24 21:12:29 +0200) (RuntimeError)\n./features/lib/helpers/wait_until.rb:28:in `rescue in wait_until'\n./features/lib/helpers/wait_until.rb:15:in `wait_until'\n./features/lib/helpers/waiter.rb:148:in `perform_until'\n./features/lib/helpers/waiter.rb:144:in `perform_until_after_delay'\n./features/step_definitions/search_page_steps.rb:78:in `/^I should not see the server_error_view$/'\nfeatures/acceptance/search/search.feature:121:in `Then I should not see the server_error_view'\n ",
148
+ "failedSince": 217,
149
+ "name": "ErrorView should not be shown when clearing search results",
150
+ "skipped": false,
151
+ "skippedMessage": null,
152
+ "status": "FAILED",
153
+ "stderr": "",
154
+ "stdout": ""
155
+ },
156
+ {
157
+ "age": 1,
158
+ "className": "Flaky Spec B",
159
+ "duration": 30.674717,
160
+ "errorDetails": "failed Going back after playing ad does not show ad again",
161
+ "errorStackTrace": "\n Scenario: Going back after playing ad does not show ad again\n\nGiven explore and player and waveform and audio_ad datasets are loaded\nAnd that the app is started\nAnd I am on explore_page\nGiven I go to \"Trending Music Track 1\" player_page\nAnd audio ad has loaded after \"Trending Music Track 1\"\nWhen I tap the next_player_page_button\nThen I should be on audio ad page\nAnd I should hear the audio ad \"MONEYZZZZ\"\nGiven I really want to wait for 4 seconds\nWhen I tap the next_player_page_button\nThen I should be on \"Trending Music Track 2\" player_page\nAnd I should hear music for track \"Trending Music Track 2\"\nAnd I should see the leave_behind\nWhen I tap the leave_behind close button\nThen I should not see the leave_behind\nWhen I tap the previous_player_page_button\nThen I should be on \"Trending Music Track 1\" player_page\n\nMessage:\n\n timed out waiting: timeout = 15, poll_sleep = 0.5 (elapsed time: 15.001166, time 2014-09-24 21:18:19 +0200) (RuntimeError)\n./features/lib/helpers/wait_until.rb:28:in `rescue in wait_until'\n./features/lib/helpers/wait_until.rb:15:in `wait_until'\n./features/step_definitions/player_page_steps.rb:63:in `/^I should be on \"(.*)\" player_page$/'\nfeatures/acceptance/player/player_audio_ad_playback.feature:52:in `Then I should be on \"Trending Music Track 1\" player_page'\n ",
162
+ "failedSince": 220,
163
+ "name": "Going back after playing ad does not show ad again",
164
+ "skipped": false,
165
+ "skippedMessage": null,
166
+ "status": "FAILED",
167
+ "stderr": "",
168
+ "stdout": ""
169
+ }
170
+ ]
@@ -0,0 +1,89 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+ =begin
44
+ # These two settings work together to allow you to limit a spec run
45
+ # to individual examples or groups you care about by tagging them with
46
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
47
+ # get run.
48
+ config.filter_run :focus
49
+ config.run_all_when_everything_filtered = true
50
+
51
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
52
+ # For more details, see:
53
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
54
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
55
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
56
+ config.disable_monkey_patching!
57
+
58
+ # This setting enables warnings. It's recommended, but in some cases may
59
+ # be too noisy due to issues in dependencies.
60
+ config.warnings = true
61
+
62
+ # Many RSpec users commonly either run the entire suite or an individual
63
+ # file, and it's useful to allow more verbose output when running an
64
+ # individual spec file.
65
+ if config.files_to_run.one?
66
+ # Use the documentation formatter for detailed output,
67
+ # unless a formatter has already been configured
68
+ # (e.g. via a command-line flag).
69
+ config.default_formatter = 'doc'
70
+ end
71
+
72
+ # Print the 10 slowest examples and example groups at the
73
+ # end of the spec run, to help surface which specs are running
74
+ # particularly slow.
75
+ config.profile_examples = 10
76
+
77
+ # Run specs in random order to surface order dependencies. If you find an
78
+ # order dependency and want to debug it, you can fix the order by providing
79
+ # the seed, which is printed after each run.
80
+ # --seed 1234
81
+ config.order = :random
82
+
83
+ # Seed global randomization in this process using the `--seed` CLI option.
84
+ # Setting this allows you to use `--seed` to deterministically reproduce
85
+ # test failures related to randomization by passing the same `--seed` value
86
+ # as the one that triggered the failure.
87
+ Kernel.srand config.seed
88
+ =end
89
+ end
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+ require 'korinthenkacker/test_case'
3
+
4
+ module Korinthenkacker
5
+ describe TestCase do
6
+ let(:test_case_a) { TestCase.new(json_a, 1) }
7
+ let(:test_case_b) { TestCase.new(json_b, 2) }
8
+
9
+ describe '#==' do
10
+ context 'with same name and same class name' do
11
+ let(:json_a) { {'name' => 'name', 'className' => 'class_name'} }
12
+ let(:json_b) { {'name' => 'name', 'className' => 'class_name'} }
13
+
14
+ it 'is considered equal' do
15
+ expect(test_case_a).to eql(test_case_b)
16
+ end
17
+ end
18
+
19
+ context 'with same name but different class name' do
20
+ let(:json_a) { {'name' => 'name', 'className' => 'class_name_a'} }
21
+ let(:json_b) { {'name' => 'name', 'className' => 'class_name_b'} }
22
+
23
+ it 'is NOT considered equal' do
24
+ expect(test_case_a).to_not eql(test_case_b)
25
+ end
26
+ end
27
+
28
+ context 'with different name but same class name' do
29
+ let(:json_a) { {'name' => 'name_a', 'className' => 'class_name'} }
30
+ let(:json_b) { {'name' => 'name_b', 'className' => 'class_name'} }
31
+
32
+ it 'is NOT considered equal' do
33
+ expect(test_case_a).to_not eql(test_case_b)
34
+ end
35
+ end
36
+
37
+ context 'with different name and different class name' do
38
+ let(:json_a) { {'name' => 'name_a', 'className' => 'class_name_a'} }
39
+ let(:json_b) { {'name' => 'name_b', 'className' => 'class_name_b'} }
40
+
41
+ it 'is NOT considered equal' do
42
+ expect(test_case_a).to_not eql(test_case_b)
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#sibling?' do
48
+ context 'when equal and same failed_since' do
49
+ let(:json_a) { {'name' => 'name', 'className' => 'class_name', 'failedSince' => 42} }
50
+ let(:json_b) { {'name' => 'name', 'className' => 'class_name', 'failedSince' => 42} }
51
+
52
+ it 'returns false' do
53
+ expect(test_case_a.sibling?(test_case_b)).to be_falsy
54
+ end
55
+ end
56
+
57
+ context 'when equal but different failed_since' do
58
+ let(:json_a) { {'name' => 'name', 'className' => 'class_name', 'failedSince' => 1} }
59
+ let(:json_b) { {'name' => 'name', 'className' => 'class_name', 'failedSince' => 42} }
60
+
61
+ it 'returns false' do
62
+ expect(test_case_a.sibling?(test_case_b)).to be_truthy
63
+ end
64
+ end
65
+
66
+ context 'when not equal and same failed_since' do
67
+ let(:json_a) { {'name' => 'nameA', 'className' => 'class_nameA', 'failedSince' => 42} }
68
+ let(:json_b) { {'name' => 'nameB', 'className' => 'class_nameB', 'failedSince' => 42} }
69
+
70
+ it 'returns false' do
71
+ expect(test_case_a.sibling?(test_case_b)).to be_falsy
72
+ end
73
+ end
74
+
75
+ context 'when not equal and different failed_since' do
76
+ let(:json_a) { {'name' => 'nameA', 'className' => 'class_nameA', 'failedSince' => 1} }
77
+ let(:json_b) { {'name' => 'nameB', 'className' => 'class_nameB', 'failedSince' => 42} }
78
+
79
+ it 'returns false' do
80
+ expect(test_case_a.sibling?(test_case_b)).to be_falsy
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: korinthenkacker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Garrigues
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-16 00:00:00.000000000 Z
11
+ date: 2014-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Find out what the fuck is going on with those tests
56
70
  email:
57
71
  - vincent@soundcloud.com
@@ -61,6 +75,7 @@ extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
63
77
  - .gitignore
78
+ - .rspec
64
79
  - .rvmrc
65
80
  - Gemfile
66
81
  - LICENSE.txt
@@ -72,10 +87,19 @@ files:
72
87
  - lib/korinthenkacker/api.rb
73
88
  - lib/korinthenkacker/cli.rb
74
89
  - lib/korinthenkacker/configuration.rb
90
+ - lib/korinthenkacker/filters/failed.rb
91
+ - lib/korinthenkacker/filters/flaky.rb
75
92
  - lib/korinthenkacker/job_info.rb
93
+ - lib/korinthenkacker/reporters/simple.rb
94
+ - lib/korinthenkacker/reporters/verbose.rb
76
95
  - lib/korinthenkacker/test_case.rb
77
96
  - lib/korinthenkacker/test_report.rb
78
97
  - lib/korinthenkacker/version.rb
98
+ - spec/filters/failed_spec.rb
99
+ - spec/filters/flaky_spec.rb
100
+ - spec/fixtures/test_cases.json
101
+ - spec/spec_helper.rb
102
+ - spec/test_case_spec.rb
79
103
  homepage: ''
80
104
  licenses:
81
105
  - MIT
@@ -96,8 +120,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
120
  version: '0'
97
121
  requirements: []
98
122
  rubyforge_project:
99
- rubygems_version: 2.0.3
123
+ rubygems_version: 2.2.1
100
124
  signing_key:
101
125
  specification_version: 4
102
126
  summary: Seriously Jenkins?
103
- test_files: []
127
+ test_files:
128
+ - spec/filters/failed_spec.rb
129
+ - spec/filters/flaky_spec.rb
130
+ - spec/fixtures/test_cases.json
131
+ - spec/spec_helper.rb
132
+ - spec/test_case_spec.rb