derailed_benchmarks 1.7.0 → 1.8.0

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
  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