korinthenkacker 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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