derailed_benchmarks 1.7.0 → 1.8.0

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
  SHA256:
3
- metadata.gz: 843a6c3b99ee45120c15af62a592e9bbecb2d88f0917fa292faf9f7128ab721a
4
- data.tar.gz: f1cdfb9c4145c27a8dac7f2c868077c6359213c3c367f2cf8a0f8909f50c9468
3
+ metadata.gz: 24d0d07556d2d6676ea8b2c06d58583071fd9302c056d36db05d252480889308
4
+ data.tar.gz: 0c783db16c1612173975c12760b101fc30cbd5765d296b2fd66506992305ac69
5
5
  SHA512:
6
- metadata.gz: 1936293cd836af98e6af454a7482534c7481b938f4944813a5261a04a9b766b0a169b866397b3d9745d25316cd8916df3b74fc1fd4f1ff46f6e5753eba92bd96
7
- data.tar.gz: 7de041007e174314699cac83eef5b70626e5447eddd705190d68d74cc7d50c0b9d660d032127cf747ea67566a096bbca4aafa05774c413da6f4ca802c8da2c08
6
+ metadata.gz: e37aa667a5fe031a384a343de999a34ef9f1e0ba98f8190d6f86d326ee30f968e3555344fbaf8841f99da85354c2b4383fe1b75ab166eb0df9134d20ee48fd95
7
+ data.tar.gz: 3f581637e835d808b8264786ffd70159fe534c4147e72e65f5649fe7ecb0b92b9edc215c9edd43e2ca51a510793ae475da3e0318262103bb741823746d9f3421
@@ -1,10 +1,13 @@
1
1
  name: Check Changelog
2
- on: [pull_request]
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
3
6
  jobs:
4
- build:
5
- runs-on: ubuntu-latest
6
- steps:
7
- - uses: actions/checkout@v1
8
- - name: Check that CHANGELOG is touched
9
- run: |
10
- cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v1
11
+ - name: Check that CHANGELOG is touched
12
+ run: |
13
+ cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
@@ -1,18 +1,20 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.5.5
4
- - 2.6.3
5
- - ruby-head
3
+ - 2.2.10
4
+ - 2.5.8
5
+ - 2.7.1
6
6
 
7
7
  gemfile:
8
8
  - gemfiles/rails_5_1.gemfile
9
- - gemfiles/rails_5_2.gemfile
10
9
  - gemfiles/rails_6_0.gemfile
11
10
  - gemfiles/rails_git.gemfile
12
11
 
13
12
  before_install:
14
- - gem install bundler
13
+ - gem install bundler -v 1.17.3
15
14
 
16
- matrix:
15
+ jobs:
17
16
  allow_failures:
18
- - rvm: ruby-head
17
+ - rvm: 2.2.10
18
+ gemfile: gemfiles/rails_6_0.gemfile
19
+ - rvm: 2.2.10
20
+ gemfile: gemfiles/rails_git.gemfile
@@ -1,4 +1,8 @@
1
- ## master (unreleased)
1
+ ## HEAD
2
+
3
+ ## 1.8.0
4
+
5
+ - Ruby 2.2 is now officialy supported and tested (https://github.com/schneems/derailed_benchmarks/pull/177)
2
6
 
3
7
  ## 1.7.0
4
8
 
data/README.md CHANGED
@@ -10,7 +10,7 @@ A series of things you can use to benchmark a Rails or Ruby app.
10
10
  ## Compatibility/Requirements
11
11
 
12
12
  This gem has been tested and is known to work with Rails 3.2+ using Ruby
13
- 2.1+. Some commands __may__ work on older versions of Ruby, but not all commands are supported.
13
+ 2.2+. Some commands __may__ work on older versions of Ruby, but not all commands are supported.
14
14
 
15
15
  For some benchmarks, not all, you'll need to verify you have a working version of curl on your OS:
16
16
 
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
21
  gem.require_paths = ["lib"]
22
22
 
23
- gem.required_ruby_version = ">= 2.1.0"
23
+ gem.required_ruby_version = ">= 2.2.0"
24
24
 
25
25
  gem.add_dependency "heapy", "~> 0"
26
26
  gem.add_dependency "memory_profiler", "~> 0"
@@ -30,8 +30,7 @@ Gem::Specification.new do |gem|
30
30
  gem.add_dependency "rake", "> 10", "< 14"
31
31
  gem.add_dependency "thor", ">= 0.19", "< 2"
32
32
  gem.add_dependency "ruby-statistics", ">= 2.1"
33
- gem.add_dependency "unicode_plot", ">= 0.0.4", "< 1.0.0"
34
- gem.add_dependency "mini_histogram", "~> 0"
33
+ gem.add_dependency "mini_histogram", ">= 0.2.1"
35
34
 
36
35
  gem.add_development_dependency "capybara", "~> 2"
37
36
  gem.add_development_dependency "m"
@@ -43,8 +43,9 @@ end
43
43
  require 'derailed_benchmarks/require_tree'
44
44
  require 'derailed_benchmarks/auth_helper'
45
45
 
46
- require 'derailed_benchmarks/stats_in_file'
46
+ require 'derailed_benchmarks/stats_for_file'
47
47
  require 'derailed_benchmarks/stats_from_dir'
48
+ require 'derailed_benchmarks/git/switch_project'
48
49
 
49
50
  if DerailedBenchmarks.gem_is_bundled?("devise")
50
51
  DerailedBenchmarks.auth = DerailedBenchmarks::AuthHelpers::Devise.new
@@ -0,0 +1,36 @@
1
+ module DerailedBenchmarks
2
+ # Represents a specific commit in a git repo
3
+ #
4
+ # Can be used to get information from the commit or to check it out
5
+ #
6
+ # commit = GitCommit.new(path: "path/to/repo", ref: "6e642963acec0ff64af51bd6fba8db3c4176ed6e")
7
+ # commit.short_sha # => "6e64296"
8
+ # commit.checkout! # Will check out the current commit at the repo in the path
9
+ class Git::Commit
10
+ attr_reader :ref, :description, :time, :short_sha, :log
11
+
12
+ def initialize(path: , ref: , log_dir: Pathname.new("/dev/null"))
13
+ @in_git_path = Git::InPath.new(path)
14
+ @ref = ref
15
+ @log = log_dir.join("#{file_safe_ref}.bench.txt")
16
+
17
+ Dir.chdir(path) do
18
+ checkout!
19
+ @description = @in_git_path.description
20
+ @short_sha = @in_git_path.short_sha
21
+ @time = @in_git_path.time
22
+ end
23
+ end
24
+
25
+ alias :desc :description
26
+ alias :file :log
27
+
28
+ def checkout!
29
+ @in_git_path.checkout!(ref)
30
+ end
31
+
32
+ private def file_safe_ref
33
+ ref.gsub('/', ':')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ module DerailedBenchmarks
2
+ # A class for running commands in a git directory
3
+ #
4
+ # It's faster to check if we're already in that directory instead
5
+ # of having to `cd` into each time. https://twitter.com/schneems/status/1305196730170961920
6
+ #
7
+ # Example:
8
+ #
9
+ # in_git_path = InGitPath.new(`bundle info heapy --path`.strip)
10
+ # in_git_path.checkout!("f0f92b06156f2274021aa42f15326da041ee9009")
11
+ # in_git_path.short_sha # => "f0f92b0"
12
+ class Git::InPath
13
+ attr_reader :path
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ end
18
+
19
+ def description
20
+ run!("git log --oneline --format=%B -n 1 HEAD | head -n 1")
21
+ end
22
+
23
+ def short_sha
24
+ run!("git rev-parse --short HEAD")
25
+ end
26
+
27
+ def time_stamp_string
28
+ run!("git log -n 1 --pretty=format:%ci") # https://stackoverflow.com/a/25921837/147390
29
+ end
30
+
31
+ def branch
32
+ branch = run!("git rev-parse --abbrev-ref HEAD")
33
+ branch == "HEAD" ? nil : branch
34
+ end
35
+
36
+ def checkout!(ref)
37
+ run!("git checkout '#{ref}' 2>&1")
38
+ end
39
+
40
+ def time
41
+ DateTime.parse(time_stamp_string)
42
+ end
43
+
44
+ def run(cmd)
45
+ if Dir.pwd == path
46
+ out = `#{cmd}`.strip
47
+ else
48
+ out = `cd #{path} && #{cmd}`.strip
49
+ end
50
+ out
51
+ end
52
+
53
+ def run!(cmd)
54
+ out = run(cmd)
55
+ raise "Error while running #{cmd.inspect}: #{out}" unless $?.success?
56
+ out
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,128 @@
1
+ module DerailedBenchmarks
2
+ class Git
3
+ end
4
+ end
5
+ require_relative "in_path.rb"
6
+ require_relative "commit.rb"
7
+
8
+ module DerailedBenchmarks
9
+ # Wraps two or more git commits in a specific location
10
+ #
11
+ # Returns an array of GitCommit objects that can be used to manipulate
12
+ # and checkout the repo
13
+ #
14
+ # Example:
15
+ #
16
+ # `git clone https://sharpstone/default_ruby tmp/default_ruby`
17
+ #
18
+ # project = GitSwitchProject.new(path: "tmp/default_ruby")
19
+ #
20
+ # By default it will represent the last two commits:
21
+ #
22
+ # project.commits.length # => 2
23
+ #
24
+ # You can pass in explicit REFs in an array:
25
+ #
26
+ # ref_array = ["da748a59340be8b950e7bbbfb32077eb67d70c3c", "9b19275a592f148e2a53b87ead4ccd8c747539c9"]
27
+ # project = GitSwitchProject.new(path: "tmp/default_ruby", ref_array: ref_array)
28
+ #
29
+ # puts project.commits.map(&:ref) == ref_array # => true
30
+ #
31
+ #
32
+ # It knows the current branch or sha:
33
+ #
34
+ # `cd tmp/ruby && git checkout -b mybranch`
35
+ # project.current_branch_or_sha #=> "mybranch"
36
+ #
37
+ # It can be used for safely wrapping checkouts to ensure the project returns to it's original branch:
38
+ #
39
+ # project.restore_branch_on_return do
40
+ # project.commits.first.checkout!
41
+ # project.current_branch_or_sha # => "da748a593"
42
+ # end
43
+ #
44
+ # project.current_branch_or_sha # => "mybranch"
45
+ class Git::SwitchProject
46
+ attr_reader :commits
47
+
48
+ def initialize(path: , ref_array: [], io: STDOUT, log_dir: "/dev/null")
49
+ @path = Pathname.new(path)
50
+
51
+ @in_git_path = Git::InPath.new(@path.expand_path)
52
+
53
+ raise "Must be a path with a .git directory '#{@path}'" if !@path.join(".git").exist?
54
+ @io = io
55
+ @commits = []
56
+ log_dir = Pathname(log_dir)
57
+
58
+ expand_refs(ref_array).each do |ref|
59
+ restore_branch_on_return(quiet: true) do
60
+ @commits << Git::Commit.new(path: @path, ref: ref, log_dir: log_dir)
61
+ end
62
+ end
63
+
64
+ if (duplicate = @commits.group_by(&:short_sha).detect {|(k, v)| v.length > 1})
65
+ raise "Duplicate SHA resolved #{duplicate[0].inspect}: #{duplicate[1].map {|c| "'#{c.ref}' => '#{c.short_sha}'"}.join(", ") } at #{@path}"
66
+ end
67
+ end
68
+
69
+ def current_branch_or_sha
70
+ branch_or_sha = @in_git_path.branch
71
+ branch_or_sha ||= @in_git_path.short_sha
72
+ branch_or_sha
73
+ end
74
+
75
+ def dirty?
76
+ !clean?
77
+ end
78
+
79
+ # https://stackoverflow.com/a/3879077/147390
80
+ def clean?
81
+ @in_git_path.run("git diff-index --quiet HEAD --") && $?.success?
82
+ end
83
+
84
+ private def status(pattern: "*.gemspec")
85
+ @in_git_path.run("git status #{pattern}")
86
+ end
87
+
88
+ def restore_branch_on_return(quiet: false)
89
+ if dirty? && status.include?("gemspec")
90
+ dirty_gemspec = true
91
+ unless quiet
92
+ @io.puts "Working tree at #{@path} is dirty, stashing. This will be popped on return"
93
+ @io.puts "Bundler modifies gemspec files on git install, this is normal"
94
+ @io.puts "Original status:\n#{status}"
95
+ end
96
+ @in_git_path.run!("git stash")
97
+ end
98
+ branch_or_sha = self.current_branch_or_sha
99
+ yield
100
+ ensure
101
+ return unless branch_or_sha
102
+ @io.puts "Resetting git dir of '#{@path.to_s}' to #{branch_or_sha.inspect}" unless quiet
103
+
104
+ @in_git_path.checkout!(branch_or_sha)
105
+ if dirty_gemspec
106
+ out = @in_git_path.run!("git stash apply 2>&1")
107
+ @io.puts "Applying stash of '#{@path.to_s}':\n#{out}" unless quiet
108
+ end
109
+ end
110
+
111
+ # case ref_array.length
112
+ # when >= 2
113
+ # returns original array
114
+ # when 1
115
+ # returns the given ref plus the one before it
116
+ # when 0
117
+ # returns the most recent 2 refs
118
+ private def expand_refs(ref_array)
119
+ return ref_array if ref_array.length >= 2
120
+
121
+ @in_git_path.checkout!(ref_array.first) if ref_array.first
122
+
123
+ branches_string = @in_git_path.run!("git log --format='%H' -n 2")
124
+ ref_array = branches_string.split($/)
125
+ return ref_array
126
+ end
127
+ end
128
+ end
@@ -40,7 +40,7 @@ module DerailedBenchmarks
40
40
  end
41
41
 
42
42
  def to_string
43
- str = +"#{name}: #{cost.round(4)} MiB"
43
+ str = String.new("#{name}: #{cost.round(4)} MiB")
44
44
  if parent && REQUIRED_BY[self.name.to_s]
45
45
  names = REQUIRED_BY[self.name.to_s].uniq - [parent.name.to_s]
46
46
  if names.any?
@@ -16,24 +16,30 @@ module DerailedBenchmarks
16
16
  # x.average # => 10.5
17
17
  # x.name # => "muhfile"
18
18
  class StatsForFile
19
- attr_reader :name, :values, :desc, :time
19
+ attr_reader :name, :values, :desc, :time, :short_sha
20
20
 
21
- def initialize(file:, name:, desc: "", time: )
21
+ def initialize(file:, name:, desc: "", time: , short_sha: nil)
22
22
  @file = Pathname.new(file)
23
23
  FileUtils.touch(@file)
24
24
 
25
25
  @name = name
26
26
  @desc = desc
27
27
  @time = time
28
+ @short_sha = short_sha
28
29
  end
29
30
 
30
31
  def call
31
32
  load_file!
33
+ return if values.empty?
32
34
 
33
35
  @median = (values[(values.length - 1) / 2] + values[values.length/ 2]) / 2.0
34
36
  @average = values.inject(:+) / values.length
35
37
  end
36
38
 
39
+ def empty?
40
+ values.empty?
41
+ end
42
+
37
43
  def median
38
44
  @median.to_f
39
45
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'bigdecimal'
4
4
  require 'statistics'
5
- require 'unicode_plot'
6
5
  require 'stringio'
7
6
  require 'mini_histogram'
7
+ require 'mini_histogram/plot'
8
8
 
9
9
  module DerailedBenchmarks
10
10
  # A class used to read several benchmark files
@@ -29,14 +29,28 @@ module DerailedBenchmarks
29
29
  FORMAT = "%0.4f"
30
30
  attr_reader :stats, :oldest, :newest
31
31
 
32
- def initialize(hash)
32
+ def initialize(input)
33
33
  @files = []
34
34
 
35
- hash.each do |branch, info_hash|
36
- file = info_hash.fetch(:file)
37
- desc = info_hash.fetch(:desc)
38
- time = info_hash.fetch(:time)
39
- @files << StatsForFile.new(file: file, desc: desc, time: time, name: branch)
35
+ if input.is_a?(Hash)
36
+ hash = input
37
+ hash.each do |branch, info_hash|
38
+ file = info_hash.fetch(:file)
39
+ desc = info_hash.fetch(:desc)
40
+ time = info_hash.fetch(:time)
41
+ short_sha = info_hash["short_sha"]
42
+ @files << StatsForFile.new(file: file, desc: desc, time: time, name: branch, short_sha: short_sha)
43
+ end
44
+ else
45
+ input.each do |commit|
46
+ @files << StatsForFile.new(
47
+ file: commit.file,
48
+ desc: commit.desc,
49
+ time: commit.time,
50
+ name: commit.ref,
51
+ short_sha: commit.short_sha
52
+ )
53
+ end
40
54
  end
41
55
  @files.sort_by! { |f| f.time }
42
56
  @oldest = @files.first
@@ -46,6 +60,8 @@ module DerailedBenchmarks
46
60
  def call
47
61
  @files.each(&:call)
48
62
 
63
+ return self if @files.detect(&:empty?)
64
+
49
65
  stats_95 = statistical_test(confidence: 95)
50
66
 
51
67
  # If default check is good, see if we also pass a more rigorous test
@@ -109,12 +125,12 @@ module DerailedBenchmarks
109
125
  MiniHistogram.set_average_edges!(newest_histogram, oldest_histogram)
110
126
 
111
127
  {newest => newest_histogram, oldest => oldest_histogram}.each do |report, histogram|
112
- plot = UnicodePlot.histogram(
113
- histogram,
114
- title: "\n#{' ' * 18 }Histogram - [#{report.name}] #{report.desc.inspect}",
128
+ plot = histogram.plot(
129
+ title: "\n#{' ' * 18 }Histogram - [#{report.short_sha || report.name}] #{report.desc.inspect}",
115
130
  ylabel: "Time (s)",
116
131
  xlabel: "# of runs in range"
117
132
  )
133
+
118
134
  plot.render(io)
119
135
  io.puts
120
136
  end
@@ -123,6 +139,8 @@ module DerailedBenchmarks
123
139
  end
124
140
 
125
141
  def banner(io = $stdout)
142
+ return if @files.detect(&:empty?)
143
+
126
144
  io.puts
127
145
  if significant?
128
146
  io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️"
@@ -130,11 +148,11 @@ module DerailedBenchmarks
130
148
  io.puts "👎👎👎(NOT Statistically Significant) 👎👎👎"
131
149
  end
132
150
  io.puts
133
- io.puts "[#{newest.name}] #{newest.desc.inspect} - (#{newest.median} seconds)"
151
+ io.puts "[#{newest.short_sha || newest.name}] (#{FORMAT % newest.median} seconds) #{newest.desc.inspect} ref: #{newest.name.inspect}"
134
152
  io.puts " #{change_direction} by:"
135
153
  io.puts " #{align}#{FORMAT % x_faster}x [older/newer]"
136
154
  io.puts " #{FORMAT % percent_faster}\% [(older - newer) / older * 100]"
137
- io.puts "[#{oldest.name}] #{oldest.desc.inspect} - (#{oldest.median} seconds)"
155
+ io.puts "[#{oldest.short_sha || oldest.name}] (#{FORMAT % oldest.median} seconds) #{oldest.desc.inspect} ref: #{oldest.name.inspect}"
138
156
  io.puts
139
157
  io.puts "Iterations per sample: #{ENV["TEST_COUNT"]}"
140
158
  io.puts "Samples: #{newest.values.length}"
@@ -17,96 +17,57 @@ namespace :perf do
17
17
  script = ENV["DERAILED_SCRIPT"] || "bundle exec derailed exec perf:test"
18
18
 
19
19
  if ENV["DERAILED_PATH_TO_LIBRARY"]
20
- library_dir = ENV["DERAILED_PATH_TO_LIBRARY"]
20
+ library_dir = ENV["DERAILED_PATH_TO_LIBRARY"].chomp
21
21
  else
22
22
  library_dir = DerailedBenchmarks.rails_path_on_disk
23
23
  end
24
-
25
- raise "Must be a path with a .git directory '#{library_dir}'" unless File.exist?(File.join(library_dir, ".git"))
26
-
27
- # Use either the explicit SHAs when present or grab last two SHAs from commit history
28
- # if only one SHA is given, then use it and the last SHA from commit history
29
- branch_names = []
30
- branch_names = ENV.fetch("SHAS_TO_TEST").split(",") if ENV["SHAS_TO_TEST"]
31
- if branch_names.length < 2
32
- Dir.chdir(library_dir) do
33
- run!("git checkout '#{branch_names.first}'") unless branch_names.empty?
34
-
35
- branches = run!('git log --format="%H" -n 2').chomp.split($/)
36
- if branch_names.empty?
37
- branch_names = branches
38
- else
39
- branches.shift
40
- branch_names << branches.shift
41
- end
42
- end
43
- end
44
-
45
- current_library_branch = ""
46
- Dir.chdir(library_dir) { current_library_branch = run!('git describe --contains --all HEAD').chomp }
24
+ library_dir = Pathname.new(library_dir)
47
25
 
48
26
  out_dir = Pathname.new("tmp/compare_branches/#{Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')}")
49
27
  out_dir.mkpath
50
28
 
51
- branches_to_test = branch_names.each_with_object({}) {|elem, hash| hash[elem] = out_dir + "#{elem.gsub('/', ':')}.bench.txt" }
52
- branch_info = {}
53
- branch_to_sha = {}
29
+ ref_string = ENV["SHAS_TO_TEST"] || ENV["REFS_TO_TEST"] || ""
54
30
 
55
- branches_to_test.each do |branch, file|
56
- Dir.chdir(library_dir) do
57
- run!("git checkout '#{branch}'")
58
- description = run!("git log --oneline --format=%B -n 1 HEAD | head -n 1").strip
59
- time_stamp = run!("git log -n 1 --pretty=format:%ci").strip # https://stackoverflow.com/a/25921837/147390
60
- short_sha = run!("git rev-parse --short HEAD").strip
61
- branch_to_sha[branch] = short_sha
31
+ project = DerailedBenchmarks::Git::SwitchProject.new(
32
+ path: library_dir,
33
+ ref_array: ref_string.split(","),
34
+ log_dir: out_dir
35
+ )
62
36
 
63
- branch_info[short_sha] = { desc: description, time: DateTime.parse(time_stamp), file: file }
64
- end
65
- run!("#{script}")
66
- end
37
+ stats = DerailedBenchmarks::StatsFromDir.new(project.commits)
67
38
 
39
+ # Advertise branch names early to make sure people know what they're testing
68
40
  puts
69
41
  puts
70
- branches_to_test.each.with_index do |(branch, _), i|
71
- short_sha = branch_to_sha[branch]
72
- desc = branch_info[short_sha][:desc]
73
- puts "Testing #{i + 1}: #{short_sha}: #{desc}"
42
+ project.commits.each_with_index do |commit, i|
43
+ puts "Testing #{i + 1}: #{commit.short_sha}: #{commit.description}"
74
44
  end
75
45
  puts
76
46
  puts
77
47
 
78
- raise "SHAs to test must be different" if branch_info.length == 1
79
- stats = DerailedBenchmarks::StatsFromDir.new(branch_info)
80
- puts "Env var no longer has any affect DERAILED_STOP_VALID_COUNT" if ENV["DERAILED_STOP_VALID_COUNT"]
48
+ project.restore_branch_on_return do
49
+ DERAILED_SCRIPT_COUNT.times do |i|
50
+ puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
51
+ project.commits.each do |commit|
52
+ commit.checkout!
81
53
 
82
- DERAILED_SCRIPT_COUNT.times do |i|
83
- puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
84
- branches_to_test.each do |branch, file|
85
- Dir.chdir(library_dir) { run!("git checkout '#{branch}'") }
86
- run!(" #{script} 2>&1 | tail -n 1 >> '#{file}'")
87
- end
54
+ output = run!("#{script} 2>&1")
55
+ commit.log.open("a") {|f| f.puts output.lines.last }
56
+ end
88
57
 
89
- if (i % 50).zero?
90
- puts "Intermediate result"
91
- stats.call
92
- stats.banner
93
- puts "Continuing execution"
58
+ if (i % 50).zero?
59
+ puts "Intermediate result"
60
+ stats.call.banner
61
+ puts "Continuing execution"
62
+ end
94
63
  end
95
64
  end
96
65
 
97
66
  ensure
98
- if library_dir && current_library_branch
99
- puts "Resetting git dir of '#{library_dir.to_s}' to #{current_library_branch.inspect}"
100
- Dir.chdir(library_dir) do
101
- run!("git checkout '#{current_library_branch}'")
102
- end
103
- end
104
-
105
67
  if stats
106
- stats.call
107
- stats.banner
68
+ stats.call.banner
108
69
 
109
- result_file = out_dir + "results.txt"
70
+ result_file = out_dir.join("results.txt")
110
71
  File.open(result_file, "w") do |f|
111
72
  stats.banner(f)
112
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DerailedBenchmarks
4
- VERSION = "1.7.0"
4
+ VERSION = "1.8.0"
5
5
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class GitSwitchProjectTest < ActiveSupport::TestCase
6
+ test "tells me when it's not pointing at a git project" do
7
+ exception = assert_raises {
8
+ DerailedBenchmarks::Git::SwitchProject.new(path: "/dev/null")
9
+ }
10
+ assert_includes(exception.message, '.git directory')
11
+ end
12
+
13
+ test "dirty gemspec cleaning" do
14
+ Dir.mktmpdir do |dir|
15
+ run!("git clone https://github.com/sharpstone/default_ruby #{dir} 2>&1 && cd #{dir} && git checkout 6e642963acec0ff64af51bd6fba8db3c4176ed6e 2>&1 && git checkout -b mybranch 2>&1")
16
+ run!("cd #{dir} && echo lol > foo.gemspec && git add .")
17
+
18
+ io = StringIO.new
19
+ project = DerailedBenchmarks::Git::SwitchProject.new(path: dir, io: io)
20
+
21
+ assert project.dirty?
22
+ refute project.clean?
23
+
24
+ project.restore_branch_on_return do
25
+ project.commits.map(&:checkout!)
26
+ end
27
+
28
+ assert_includes io.string, "Bundler modifies gemspec files"
29
+ assert_includes io.string, "Applying stash"
30
+ end
31
+ end
32
+
33
+ test "works on a git repo" do
34
+ Dir.mktmpdir do |dir|
35
+ run!("git clone https://github.com/sharpstone/default_ruby #{dir} 2>&1 && cd #{dir} && git checkout 6e642963acec0ff64af51bd6fba8db3c4176ed6e 2>&1 && git checkout -b mybranch 2>&1")
36
+
37
+ # finds shas when none given
38
+ project = DerailedBenchmarks::Git::SwitchProject.new(path: dir)
39
+
40
+ assert_equal ["6e642963acec0ff64af51bd6fba8db3c4176ed6e", "da748a59340be8b950e7bbbfb32077eb67d70c3c"], project.commits.map(&:ref)
41
+ first_commit = project.commits.first
42
+
43
+ assert_equal "CI test support", first_commit.description
44
+ assert_equal "6e64296", first_commit.short_sha
45
+ assert_equal "/dev/null/6e642963acec0ff64af51bd6fba8db3c4176ed6e.bench.txt", first_commit.log.to_s
46
+ assert_equal DateTime.parse("Tue, 14 Apr 2020 13:26:03 -0500"), first_commit.time
47
+
48
+ assert_equal "mybranch", project.current_branch_or_sha
49
+
50
+ # Finds shas when 1 is given
51
+ project = DerailedBenchmarks::Git::SwitchProject.new(path: dir, ref_array: ["da748a59340be8b950e7bbbfb32077eb67d70c3c"])
52
+
53
+ assert_equal ["da748a59340be8b950e7bbbfb32077eb67d70c3c", "5c09f748957d2098182762004adee27d1ff83160"], project.commits.map(&:ref)
54
+
55
+
56
+ # Returns correct refs if given
57
+ project = DerailedBenchmarks::Git::SwitchProject.new(path: dir, ref_array: ["da748a59340be8b950e7bbbfb32077eb67d70c3c", "9b19275a592f148e2a53b87ead4ccd8c747539c9"])
58
+
59
+ assert_equal ["da748a59340be8b950e7bbbfb32077eb67d70c3c", "9b19275a592f148e2a53b87ead4ccd8c747539c9"], project.commits.map(&:ref)
60
+
61
+ first_commit = project.commits.first
62
+
63
+ first_commit.checkout!
64
+
65
+ assert_equal first_commit.short_sha, project.current_branch_or_sha
66
+
67
+ # Test restore_branch_on_return
68
+ project.restore_branch_on_return(quiet: true) do
69
+ project.commits.last.checkout!
70
+
71
+ assert_equal project.commits.last.short_sha, project.current_branch_or_sha
72
+ end
73
+
74
+ assert_equal project.commits.first.short_sha, project.current_branch_or_sha
75
+
76
+ exception = assert_raise {
77
+ DerailedBenchmarks::Git::SwitchProject.new(path: dir, ref_array: ["6e642963acec0ff64af51bd6fba8db3c4176ed6e", "mybranch"])
78
+ }
79
+
80
+ assert_includes(exception.message, 'Duplicate SHA resolved "6e64296"')
81
+ end
82
+ end
83
+ end
@@ -3,6 +3,19 @@
3
3
  require 'test_helper'
4
4
 
5
5
  class StatsFromDirTest < ActiveSupport::TestCase
6
+ test "empty files" do
7
+ Dir.mktmpdir do |dir|
8
+ dir = Pathname.new(dir)
9
+ branch_info = {}
10
+ branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser"), name: "loser" }
11
+ branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner"), name: "winner" }
12
+ stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
13
+ io = StringIO.new
14
+ stats.call.banner(io: io) # Doesn't error
15
+ assert_equal "", io.read
16
+ end
17
+ end
18
+
6
19
  test "that it works" do
7
20
  dir = fixtures_dir("stats/significant")
8
21
  branch_info = {}
@@ -38,7 +51,6 @@ class StatsFromDirTest < ActiveSupport::TestCase
38
51
 
39
52
  io = StringIO.new
40
53
  stats.call.banner(io)
41
- puts io.string
42
54
 
43
55
  assert_match(/11\.2 , 11\.28/, io.string)
44
56
  assert_match(/11\.8 , 11\.88/, io.string)
@@ -64,10 +76,13 @@ class StatsFromDirTest < ActiveSupport::TestCase
64
76
 
65
77
  test "banner faster" do
66
78
  dir = fixtures_dir("stats/significant")
67
- branch_info = {}
68
- branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
69
- branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
70
- stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
79
+ Branch_info = {}
80
+
81
+ require 'ostruct'
82
+ commits = []
83
+ commits << OpenStruct.new({ desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), ref: "loser", short_sha: "aaaaa" })
84
+ commits << OpenStruct.new({ desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), ref: "winner", short_sha: "bbbbb" })
85
+ stats = DerailedBenchmarks::StatsFromDir.new(commits).call
71
86
  newest = stats.newest
72
87
  oldest = stats.oldest
73
88
 
@@ -88,12 +103,12 @@ class StatsFromDirTest < ActiveSupport::TestCase
88
103
  11.0
89
104
  end
90
105
 
91
- expected = <<~EOM
92
- [winner] "I am the new commit" - (10.5 seconds)
106
+ expected = <<-EOM
107
+ [bbbbb] (10.5000 seconds) "I am the new commit" ref: "winner"
93
108
  FASTER 🚀🚀🚀 by:
94
109
  1.0476x [older/newer]
95
110
  4.5455% [(older - newer) / older * 100]
96
- [loser] "Old commit" - (11.0 seconds)
111
+ [aaaaa] (11.0000 seconds) "Old commit" ref: "loser"
97
112
  EOM
98
113
 
99
114
  actual = StringIO.new
@@ -119,12 +134,12 @@ EOM
119
134
  11.0
120
135
  end
121
136
 
122
- expected = <<~EOM
123
- [loser] "I am the new commit" - (11.0 seconds)
137
+ expected = <<-EOM
138
+ [loser] (11.0000 seconds) "I am the new commit" ref: "loser"
124
139
  SLOWER 🐢🐢🐢 by:
125
140
  0.9545x [older/newer]
126
141
  -4.7619% [(older - newer) / older * 100]
127
- [winner] "Old commit" - (10.5 seconds)
142
+ [winner] (10.5000 seconds) "Old commit" ref: "winner"
128
143
  EOM
129
144
 
130
145
  actual = StringIO.new
@@ -1,4 +1,4 @@
1
1
  class ChildOne
2
- @retained = +""
2
+ @retained = String.new("")
3
3
  50_000.times.map { @retained << "A" }
4
4
  end
@@ -1,5 +1,5 @@
1
1
  class ChildTwo
2
- @retained = +""
2
+ @retained = String.new("")
3
3
  200_000.times.map { @retained << "A" }
4
4
  end
5
5
 
@@ -1,5 +1,5 @@
1
1
  class ParentOne
2
- @retained = +""
2
+ @retained = String.new("")
3
3
  1_000_000.times.map { @retained << "A" }
4
4
  end
5
5
  require File.expand_path('../child_one.rb', __FILE__)
@@ -26,21 +26,45 @@ class TasksTest < ActiveSupport::TestCase
26
26
  env_string = env.map {|key, value| "#{key.shellescape}=#{value.to_s.shellescape}" }.join(" ")
27
27
  cmd = "env #{env_string} bundle exec rake -f perf.rake #{cmd} --trace"
28
28
  puts "Running: #{cmd}"
29
- result = Bundler.with_original_env { `cd '#{rails_app_path}' && #{cmd}` }
30
- if assert_success
31
- assert $?.success?, "Expected '#{cmd}' to return a success status.\nOutput: #{result}"
29
+ result = Bundler.with_original_env { `cd '#{rails_app_path}' && #{cmd} 2>&1` }
30
+ if assert_success && !$?.success?
31
+ puts result
32
+ raise "Expected '#{cmd}' to return a success status.\nOutput: #{result}"
32
33
  end
33
34
 
34
35
  result
35
36
  end
36
37
 
37
- test 'library_branches' do
38
+ test 'non-rails library with branch specified' do
39
+ skip unless ENV['USING_RAILS_WICKED_BRANCH']
40
+
41
+ gem_path = run!("bundle info wicked --path")
42
+ env = { "TEST_COUNT" => 10, "DERAILED_SCRIPT_COUNT" => 2, "DERAILED_PATH_TO_LIBRARY" => gem_path}
43
+ puts rake "perf:library", { env: env }
44
+ end
45
+
46
+ test 'rails perf:library from git' do
47
+ # BUNDLE_GEMFILE="$(pwd)/gemfiles/rails_git.gemfile" bundle exec m test/integration/tasks_test.rb:<linenumber>
48
+
38
49
  skip unless ENV['USING_RAILS_GIT']
39
50
 
40
- env = { "TEST_COUNT" => 10, "DERAILED_SCRIPT_COUNT" => 2, "SHAS_TO_TEST" => "3054e1d584e7eca110c69a1f8423f2e0866abbf9,80f989aecece1a2b1830e9c953e5887421b52d3c"}
51
+ env = { "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2, "SHAS_TO_TEST" => "3054e1d584e7eca110c69a1f8423f2e0866abbf9,80f989aecece1a2b1830e9c953e5887421b52d3c"}
41
52
  puts rake "perf:library", { env: env }
42
53
  end
43
54
 
55
+ test "rails perf:library with bad script" do
56
+ # BUNDLE_GEMFILE="$(pwd)/gemfiles/rails_git.gemfile" bundle exec m test/integration/tasks_test.rb:<linenumber>
57
+
58
+ skip unless ENV['USING_RAILS_GIT']
59
+
60
+ error = assert_raises {
61
+ env = { "DERAILED_SCRIPT" => "nopenopenop", "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2, "SHAS_TO_TEST" => "3054e1d584e7eca110c69a1f8423f2e0866abbf9,80f989aecece1a2b1830e9c953e5887421b52d3c"}
62
+ puts rake "perf:library", { env: env }
63
+ }
64
+
65
+ assert error.message =~ /nopenopenop:( command)? not found/, "Expected #{error.message} to include /nopenopenop: (command)? not found/ but it did not"
66
+ end
67
+
44
68
  test 'hitting authenticated devise apps' do
45
69
  env = { "PATH_TO_HIT" => "authenticated", "USE_AUTH" => "true", "TEST_COUNT" => "2" }
46
70
  result = rake 'perf:test', env: env
@@ -63,3 +63,9 @@ end
63
63
  def rails_app_path(name = "")
64
64
  root_path("test/rails_app").join(name)
65
65
  end
66
+
67
+ def run!(cmd)
68
+ output = `#{cmd}`
69
+ raise "Cmd #{cmd} failed:\n#{output}" unless $?.success?
70
+ output
71
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: derailed_benchmarks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-25 00:00:00.000000000 Z
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: heapy
@@ -135,39 +135,19 @@ dependencies:
135
135
  - !ruby/object:Gem::Version
136
136
  version: '2.1'
137
137
  - !ruby/object:Gem::Dependency
138
- name: unicode_plot
138
+ name: mini_histogram
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - ">="
142
142
  - !ruby/object:Gem::Version
143
- version: 0.0.4
144
- - - "<"
145
- - !ruby/object:Gem::Version
146
- version: 1.0.0
143
+ version: 0.2.1
147
144
  type: :runtime
148
145
  prerelease: false
149
146
  version_requirements: !ruby/object:Gem::Requirement
150
147
  requirements:
151
148
  - - ">="
152
149
  - !ruby/object:Gem::Version
153
- version: 0.0.4
154
- - - "<"
155
- - !ruby/object:Gem::Version
156
- version: 1.0.0
157
- - !ruby/object:Gem::Dependency
158
- name: mini_histogram
159
- requirement: !ruby/object:Gem::Requirement
160
- requirements:
161
- - - "~>"
162
- - !ruby/object:Gem::Version
163
- version: '0'
164
- type: :runtime
165
- prerelease: false
166
- version_requirements: !ruby/object:Gem::Requirement
167
- requirements:
168
- - - "~>"
169
- - !ruby/object:Gem::Version
170
- version: '0'
150
+ version: 0.2.1
171
151
  - !ruby/object:Gem::Dependency
172
152
  name: capybara
173
153
  requirement: !ruby/object:Gem::Requirement
@@ -277,13 +257,18 @@ files:
277
257
  - lib/derailed_benchmarks/auth_helper.rb
278
258
  - lib/derailed_benchmarks/auth_helpers/devise.rb
279
259
  - lib/derailed_benchmarks/core_ext/kernel_require.rb
260
+ - lib/derailed_benchmarks/git/commit.rb
261
+ - lib/derailed_benchmarks/git/in_path.rb
262
+ - lib/derailed_benchmarks/git/switch_project.rb
263
+ - lib/derailed_benchmarks/git_switch_project.rb
280
264
  - lib/derailed_benchmarks/load_tasks.rb
281
265
  - lib/derailed_benchmarks/require_tree.rb
266
+ - lib/derailed_benchmarks/stats_for_file.rb
282
267
  - lib/derailed_benchmarks/stats_from_dir.rb
283
- - lib/derailed_benchmarks/stats_in_file.rb
284
268
  - lib/derailed_benchmarks/tasks.rb
285
269
  - lib/derailed_benchmarks/version.rb
286
270
  - test/derailed_benchmarks/core_ext/kernel_require_test.rb
271
+ - test/derailed_benchmarks/git_switch_project_test.rb
287
272
  - test/derailed_benchmarks/require_tree_test.rb
288
273
  - test/derailed_benchmarks/stats_from_dir_test.rb
289
274
  - test/derailed_test.rb
@@ -348,7 +333,7 @@ homepage: https://github.com/schneems/derailed_benchmarks
348
333
  licenses:
349
334
  - MIT
350
335
  metadata: {}
351
- post_install_message:
336
+ post_install_message:
352
337
  rdoc_options: []
353
338
  require_paths:
354
339
  - lib
@@ -356,7 +341,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
356
341
  requirements:
357
342
  - - ">="
358
343
  - !ruby/object:Gem::Version
359
- version: 2.1.0
344
+ version: 2.2.0
360
345
  required_rubygems_version: !ruby/object:Gem::Requirement
361
346
  requirements:
362
347
  - - ">="
@@ -364,11 +349,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
364
349
  version: '0'
365
350
  requirements: []
366
351
  rubygems_version: 3.1.2
367
- signing_key:
352
+ signing_key:
368
353
  specification_version: 4
369
354
  summary: Benchmarks designed to performance test your ENTIRE site
370
355
  test_files:
371
356
  - test/derailed_benchmarks/core_ext/kernel_require_test.rb
357
+ - test/derailed_benchmarks/git_switch_project_test.rb
372
358
  - test/derailed_benchmarks/require_tree_test.rb
373
359
  - test/derailed_benchmarks/stats_from_dir_test.rb
374
360
  - test/derailed_test.rb