Skip to content

Commit

Permalink
📈 Add benchmark rake task to compare gem versions
Browse files Browse the repository at this point in the history
To compare against the latest release:
```console
$ rake benchmarks:compare
```

To compare against several different versions:
```console
$ rake benchmarks:compare[0.1.1,0.2.2,0.3.7,0.4.2]
```

To filter or apply any other `benchmark-driver` arguments:
```console
$ rake benchmarks:compare BENCHMARK_ARGS="--filter 'fetch|search'"
```
  • Loading branch information
nevans committed Oct 26, 2023
1 parent 095d396 commit 48bcb87
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/spec/reports/
/tmp/
/Gemfile.lock
benchmarks/Gemfile*
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ gem "rake"
gem "rdoc"
gem "test-unit"
gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"

gem "benchmark-driver"
1 change: 1 addition & 0 deletions benchmarks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gemfile-*.lock
52 changes: 0 additions & 52 deletions benchmarks/generate_parser_benchmarks

This file was deleted.

15 changes: 15 additions & 0 deletions benchmarks/parser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ benchmark:
response = load_response("../test/net/imap/fixtures/response_parser/body_structure_responses.yml",
"test_bodystructure_bug8167_delivery_status_with_extra_data")
script: parser.parse(response)
- name: bodystructure_extension_fields
prelude: |2
response = load_response("../test/net/imap/fixtures/response_parser/body_structure_responses.yml",
"test_bodystructure_extension_fields")
script: parser.parse(response)
- name: bodystructure_mixed_boundary
prelude: |2
response = load_response("../test/net/imap/fixtures/response_parser/body_structure_responses.yml",
Expand Down Expand Up @@ -156,6 +161,16 @@ benchmark:
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",
"test_invalid_noop_response_is_ignored")
script: parser.parse(response)
- name: invalid_noop_response_with_numeric_prefix
prelude: |2
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",
"test_invalid_noop_response_with_numeric_prefix")
script: parser.parse(response)
- name: invalid_noop_response_with_unparseable_data
prelude: |2
response = load_response("../test/net/imap/fixtures/response_parser/quirky_behaviors.yml",
"test_invalid_noop_response_with_unparseable_data")
script: parser.parse(response)
- name: invalid_search_response_multiple_result_with_trailing_space
prelude: |2
response = load_response("../test/net/imap/fixtures/response_parser/search_responses.yml",
Expand Down
3 changes: 2 additions & 1 deletion net-imap.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(bin|test|spec|features|rfcs)/}) }
`git ls-files -z 2>/dev/null`.split("\x0")
.reject {|f| f.match(%r{^(bin|test|spec|benchmarks|features|rfcs)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
Expand Down
98 changes: 98 additions & 0 deletions rakelib/benchmarks.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

PARSER_TEST_FIXTURES = FileList.new "test/net/imap/fixtures/response_parser/*.yml"
CLOBBER.include "benchmarks/parser.yml"
CLEAN.include "benchmarks/Gemfile-*"

BENCHMARK_INIT = <<RUBY
require "yaml"
require "net/imap"
def load_response(file, name)
YAML.unsafe_load_file(file).dig(:tests, name, :response)
.force_encoding "ASCII-8BIT" \\
or abort "ERRORO: missing %p fixture data in %p" % [name, file]
end
parser = Net::IMAP::ResponseParser.new
RUBY

PER_BENCHMARK_PRELUDE = <<RUBY
response = load_response(%p,
%p)
RUBY

file "benchmarks/parser.yml" => PARSER_TEST_FIXTURES do |t|
require "yaml"
require "pathname"
require "net/imap"

path = Pathname.new(__dir__) / "../test/net/imap/fixtures/response_parser"
files = path.glob("*.yml")
tests = files.flat_map {|file|
file.read
.gsub(%r{([-:]) !ruby/struct:\S+}) { $1 }
.then {
YAML.safe_load(_1, filename: file,
permitted_classes: [Symbol, Regexp], aliases: true)
}
.fetch(:tests)
.select {|test_name, test|
:parser_assert_equal == test.fetch(:test_type) {
test.key?(:expected) ? :parser_assert_equal : :parser_pending
}
}
.map {|test_name, _|
[file.relative_path_from(__dir__).to_s, test_name.to_s]
}
}

benchmarks = tests.map {|file, fixture_name|
{"name" => fixture_name.delete_prefix("test_"),
"prelude" => PER_BENCHMARK_PRELUDE % [file, fixture_name],
"script" => "parser.parse(response)"}
}
.sort_by { _1["name"] }

YAML.dump({"prelude" => BENCHMARK_INIT, "benchmark" => benchmarks})
.then { File.write t.name, _1 }
end

namespace :benchmarks do
desc "Generate benchmarks from fixture data"
task :generate => "benchmarks/parser.yml"

desc "run the parser benchmarks comparing multiple gem versions"
task :compare => :generate do |task, args|
cd Pathname.new(__dir__) + ".."
current = `git describe --tags --dirty`.chomp
current = "dev" if current.empty?
versions = args.to_a
if versions.empty?
latest = %x{git describe --tags --abbrev=0 --match 'v*.*.*'}.chomp
versions = latest.empty? ? [] : [latest.delete_prefix("v")]
end
versions = versions.to_h { [_1, "Gemfile-v#{_1}"] }
cd "benchmarks" do
versions.each do |version, gemfile|
File.write gemfile, <<~RUBY
# frozen_string_literal: true
source "https://rubygems.org"
gem "net-imap", #{version.dump}
RUBY
end
versions = {current => "../Gemfile" , **versions}.map {
"%s::/usr/bin/env BUNDLE_GEMFILE=%s ruby" % _1
}.join(";")

extra = ENV.fetch("BENCHMARK_ARGS", "").shellsplit

sh("benchmark-driver",
"--bundler",
"-e", versions,
"parser.yml",
*extra)
end
end

end

0 comments on commit 48bcb87

Please sign in to comment.