From 0fa0de3b1087f2d54c76aeac9e33dea7c0d0e598 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Thu, 27 Mar 2025 13:24:38 -0500 Subject: [PATCH 1/7] Ensure workflow uses same bundler version (#1568) * Ensure workflow uses same bundler version * Fix test --- .github/workflows/hatchet_app_cleaner.yml | 2 +- spec/helpers/config_spec.rb | 8 ++++---- spec/helpers/outdated_ruby_version_spec.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hatchet_app_cleaner.yml b/.github/workflows/hatchet_app_cleaner.yml index a7298455c..1e810bd47 100644 --- a/.github/workflows/hatchet_app_cleaner.yml +++ b/.github/workflows/hatchet_app_cleaner.yml @@ -24,7 +24,7 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - ruby-version: "3.1" + ruby-version: "3.1.6" - name: Run Hatchet destroy # Only apps older than 10 minutes are destroyed, to ensure that any # in progress CI runs are not interrupted. diff --git a/spec/helpers/config_spec.rb b/spec/helpers/config_spec.rb index eaa8d2b9c..8f4f01a87 100644 --- a/spec/helpers/config_spec.rb +++ b/spec/helpers/config_spec.rb @@ -9,9 +9,9 @@ expect(`ruby -v`).to match(Regexp.escape(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER)) - # bootstrap_version = Gem::Version.new(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER) - # default_version = Gem::Version.new(LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER) - - # expect(bootstrap_version).to be >= default_version + ci_task = Pathname(".github").join("workflows").join("hatchet_app_cleaner.yml").read + ci_task_yml = YAML.load(ci_task) + task = ci_task_yml["jobs"]["hatchet-app-cleaner"]["steps"].detect {|step| step["uses"].match?(/ruby\/setup-ruby/)} or raise "Not found" + expect(task["with"]["ruby-version"]).to match(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER) end end diff --git a/spec/helpers/outdated_ruby_version_spec.rb b/spec/helpers/outdated_ruby_version_spec.rb index 6aa49fe6e..6c24ecf49 100644 --- a/spec/helpers/outdated_ruby_version_spec.rb +++ b/spec/helpers/outdated_ruby_version_spec.rb @@ -19,7 +19,7 @@ ) outdated.call - expect(outdated.suggested_ruby_minor_version).to eq("3.1.6") + expect(outdated.suggested_ruby_minor_version).to eq("3.1.7") end it "handles arm 💪 architecture on heroku-24" do From 5f9b8ecea768a1c2fc61f45a5de6ed296b163b1d Mon Sep 17 00:00:00 2001 From: Pablo Temporini Date: Thu, 3 Apr 2025 11:06:10 -0300 Subject: [PATCH 2/7] Update repo metadata (#1572) Signed-off-by: Pablo Temporini --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2fd0356d9..fde389a62 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,6 @@ # Default to requesting pull request reviews from the Heroku Languages team. +#ECCN:Open Source +#GUSINFO:Languages,Heroku Ruby Platform * @heroku/languages # However, request review from the language owner instead for files that are updated From dfe459b9f4aa4175bc3a3ac46c308dfc6877ac52 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Thu, 3 Apr 2025 13:11:26 -0500 Subject: [PATCH 3/7] Add observability (#1569) * Add Heroku YAML report builder This utility class writes a yaml file for capturing observability metrics. * Add Ruby version metrics to global report * Implement bin/report * Report bundler version * Observe railties and rack versions * Changelog * Document error tolerance behavior * Update captured ruby version information * Fix tests * Document RubyVersion accessors * Cleaner major ruby version * Remove unused legacy ruby logic We no longer provide Ruby 1.9.2 * Silence warnings --- CHANGELOG.md | 2 +- bin/report | 13 ++++ bin/support/ruby_compile | 7 ++ lib/heroku_build_report.rb | 64 +++++++++++++++++++ lib/language_pack.rb | 2 + lib/language_pack/base.rb | 1 + lib/language_pack/helpers/bundler_wrapper.rb | 29 +++++++-- .../installers/heroku_ruby_installer.rb | 11 +++- lib/language_pack/ruby.rb | 4 ++ lib/language_pack/ruby_version.rb | 49 +++++++++----- spec/hatchet/ruby_spec.rb | 38 ++++++++++- spec/helpers/bundler_wrapper_spec.rb | 16 ++++- spec/helpers/heroku_build_report_spec.rb | 26 ++++++++ spec/installers/heroku_ruby_installer_spec.rb | 45 +++++++++++-- 14 files changed, 276 insertions(+), 31 deletions(-) create mode 100755 bin/report create mode 100644 lib/heroku_build_report.rb create mode 100644 spec/helpers/heroku_build_report_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d396e34..c12801703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ ## [Unreleased] +- Added observability metrics (https://github.com/heroku/heroku-buildpack-ruby/pull/1569) ## [v297] - 2025-03-26 - Ruby 3.1.7 and 3.2.8 is now available - ## [v296] - 2025-03-21 - Bundler `1.x` usage error is downgraded to a warning. This warning will be moved to an error once `heroku-20` is Sunset (https://github.com/heroku/heroku-buildpack-ruby/pull/1565) diff --git a/bin/report b/bin/report new file mode 100755 index 000000000..7e7a33331 --- /dev/null +++ b/bin/report @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CACHE_DIR="${2}" +REPORT_FILE="${CACHE_DIR}/.heroku/ruby/build_report.yml" + +# Whilst the release file is always written by the buildpack, some apps use +# third-party slug cleaner buildpacks to remove this and other files, so we +# cannot assume it still exists by the time the release step runs. +if [[ -f "${REPORT_FILE}" ]]; then + cat "${REPORT_FILE}" +fi diff --git a/bin/support/ruby_compile b/bin/support/ruby_compile index 7d5b3e1fe..83c2c39f4 100755 --- a/bin/support/ruby_compile +++ b/bin/support/ruby_compile @@ -10,6 +10,13 @@ $stdout.sync = true $:.unshift File.expand_path("../../../lib", __FILE__) require "language_pack" require "language_pack/shell_helpers" +HerokuBuildReport.set_global( + # Coupled with `bin/report` + path: Pathname(ARGV[1]) + .join(".heroku") + .join("ruby") + .join("build_report.yml") +).tap(&:clear!) begin LanguagePack::ShellHelpers.initialize_env(ARGV[2]) diff --git a/lib/heroku_build_report.rb b/lib/heroku_build_report.rb new file mode 100644 index 000000000..1fa2e6229 --- /dev/null +++ b/lib/heroku_build_report.rb @@ -0,0 +1,64 @@ +require 'yaml' +require 'pathname' + +# Observability reporting for builds +# +# Example usage: +# +# HerokuBuildReport::GLOBAL.capture( +# "ruby_version" => "3.4.2" +# ) +module HerokuBuildReport + # Accumulates data in memory and writes it to the specified path in YAML format + # + # Writes data to disk on every capture. Later `bin/report` emits the disk contents + class YamlReport + attr_reader :data + + def initialize(path: ) + @path = Pathname(path).expand_path + @path.dirname.mkpath + FileUtils.touch(@path) + @data = {} + end + + def clear! + @data.clear + @path.write("") + end + + def capture(metrics = {}) + metrics.each do |(key, value)| + return if key.nil? || key.to_s.strip.empty? + + key = key&.strip + raise "Key cannot be empty (#{key.inspect} => #{value})" if key.nil? || key.empty? + + @data["#{key}"] = value + end + + @path.write(@data.to_yaml) + end + end + + # Current load order of the various "language packs" + def self.set_global(path: ) + YamlReport.new(path: path).tap { |report| + # Silence warning about setting a constant + begin + old_verbose = $VERBOSE + $VERBOSE = nil + const_set(:GLOBAL, report) + ensure + $VERBOSE = old_verbose + end + } + end + + # Stores data in memory only, does not persist to disk + def self.dev_null + YamlReport.new(path: "/dev/null") + end + + GLOBAL = self.dev_null # Changed via `set_global` +end diff --git a/lib/language_pack.rb b/lib/language_pack.rb index 546194803..ae88f58d0 100644 --- a/lib/language_pack.rb +++ b/lib/language_pack.rb @@ -23,6 +23,8 @@ def self.detect(*args) $:.unshift File.expand_path("../../vendor", __FILE__) $:.unshift File.expand_path("..", __FILE__) +require 'heroku_build_report' + require 'language_pack/shell_helpers' require "language_pack/helpers/plugin_installer" require "language_pack/helpers/stale_file_cleaner" diff --git a/lib/language_pack/base.rb b/lib/language_pack/base.rb index c0fdea319..717f5c363 100644 --- a/lib/language_pack/base.rb +++ b/lib/language_pack/base.rb @@ -36,6 +36,7 @@ def initialize(build_path, cache_path = nil) @id = Digest::SHA1.hexdigest("#{Time.now.to_f}-#{rand(1000000)}")[0..10] @fetchers = {:buildpack => LanguagePack::Fetcher.new(VENDOR_URL) } @arch = get_arch + @report = HerokuBuildReport::GLOBAL Dir.chdir build_path end diff --git a/lib/language_pack/helpers/bundler_wrapper.rb b/lib/language_pack/helpers/bundler_wrapper.rb index 224732f32..7c7689bc4 100644 --- a/lib/language_pack/helpers/bundler_wrapper.rb +++ b/lib/language_pack/helpers/bundler_wrapper.rb @@ -62,11 +62,10 @@ class LanguagePack::Helpers::BundlerWrapper end end - def self.detect_bundler_version(contents: ) - version_match = contents.match(BUNDLED_WITH_REGEX) - if version_match - major = version_match[:major] - minor = version_match[:minor] + def self.detect_bundler_version(contents: , bundled_with: contents.match(BUNDLED_WITH_REGEX)) + if bundled_with + major = bundled_with[:major] + minor = bundled_with[:minor] version = BLESSED_BUNDLER_VERSIONS["#{major}.#{minor}"] version else @@ -74,7 +73,7 @@ def self.detect_bundler_version(contents: ) end end - BUNDLED_WITH_REGEX = /^BUNDLED WITH$(\r?\n) (?\d+)\.(?\d+)\.\d+/m + BUNDLED_WITH_REGEX = /^BUNDLED WITH$(\r?\n) (?(?\d+)\.(?\d+)\.\d+)/m class GemfileParseError < BuildpackError def initialize(error) @@ -105,12 +104,28 @@ def initialize(version_hash, major_minor) attr_reader :bundler_path def initialize(options = {}) + @report = options[:report] || HerokuBuildReport::GLOBAL @bundler_tmp = Pathname.new(Dir.mktmpdir) @fetcher = options[:fetcher] || LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL) # coupling @gemfile_path = options[:gemfile_path] || Pathname.new("./Gemfile") @gemfile_lock_path = Pathname.new("#{@gemfile_path}.lock") - @version = self.class.detect_bundler_version(contents: @gemfile_lock_path.read(mode: "rt")) + contents = @gemfile_lock_path.read(mode: "rt") + bundled_with = contents.match(BUNDLED_WITH_REGEX) + @report.capture( + "bundled_with" => bundled_with&.[]("version") || "empty" + ) + @version = self.class.detect_bundler_version( + contents: contents, + bundled_with: bundled_with + ) + parts = @version.split(".") + @report.capture( + "bundler_version_installed" => @version, + "bundler_major" => parts&.shift, + "bundler_minor" => parts&.shift, + "bundler_patch" => parts&.shift + ) @dir_name = "bundler-#{@version}" @bundler_path = options[:bundler_path] || @bundler_tmp.join(@dir_name) diff --git a/lib/language_pack/installers/heroku_ruby_installer.rb b/lib/language_pack/installers/heroku_ruby_installer.rb index 2b2d4b976..7d159f8d6 100644 --- a/lib/language_pack/installers/heroku_ruby_installer.rb +++ b/lib/language_pack/installers/heroku_ruby_installer.rb @@ -10,7 +10,8 @@ class LanguagePack::Installers::HerokuRubyInstaller include LanguagePack::ShellHelpers attr_reader :fetcher - def initialize(stack: , multi_arch_stacks: , arch: ) + def initialize(stack: , multi_arch_stacks: , arch: , report: HerokuBuildReport::GLOBAL) + @report = report if multi_arch_stacks.include?(stack) @fetcher = LanguagePack::Fetcher.new(BASE_URL, stack: stack, arch: arch) else @@ -19,6 +20,14 @@ def initialize(stack: , multi_arch_stacks: , arch: ) end def install(ruby_version, install_dir) + @report.capture( + "ruby.version" => ruby_version.ruby_version, + "ruby.engine" => ruby_version.engine, + "ruby.engine.version" => ruby_version.engine_version, + "ruby.major" => ruby_version.major, + "ruby.minor" => ruby_version.minor, + "ruby.patch" => ruby_version.patch, + ) fetch_unpack(ruby_version, install_dir) setup_binstubs(install_dir) end diff --git a/lib/language_pack/ruby.rb b/lib/language_pack/ruby.rb index 7c24de80f..4a16ccd05 100644 --- a/lib/language_pack/ruby.rb +++ b/lib/language_pack/ruby.rb @@ -97,6 +97,10 @@ def compile install_binaries run_assets_precompile_rake_task end + @report.capture( + "railties_version" => bundler.gem_version('railties'), + "rack_version" => bundler.gem_version('rack') + ) config_detect best_practice_warnings warn_outdated_ruby diff --git a/lib/language_pack/ruby_version.rb b/lib/language_pack/ruby_version.rb index a244b25d7..8e7913aba 100644 --- a/lib/language_pack/ruby_version.rb +++ b/lib/language_pack/ruby_version.rb @@ -15,8 +15,6 @@ def initialize(output = "") BOOTSTRAP_VERSION_NUMBER = "3.1.6".freeze DEFAULT_VERSION_NUMBER = "3.3.7".freeze DEFAULT_VERSION = "ruby-#{DEFAULT_VERSION_NUMBER}".freeze - LEGACY_VERSION_NUMBER = "1.9.2".freeze - LEGACY_VERSION = "ruby-#{LEGACY_VERSION_NUMBER}".freeze RUBY_VERSION_REGEX = %r{ (?\d+\.\d+\.\d+){0} (?p-?\d+){0} @@ -26,7 +24,25 @@ def initialize(output = "") ruby-\g(-\g)?(-\g-\g)? }x - attr_reader :set, :version, :version_without_patchlevel, :patchlevel, :engine, :ruby_version, :engine_version + + # `version` is the bundler output like `ruby-3.4.2` + attr_reader :version, + # `set` is either `:gemfile` when the app specified a version or `nil` when using + # the default version + :set, + # `version_without_patchlevel` removes any `-p` as they're not significant + # effectively this is `version_for_download` + :version_without_patchlevel, + # `patchlevel` is the `-p` or is empty + :patchlevel, + # `engine` is `:ruby` or `:jruby` + :engine, + # `ruby_version` is `..` extracted from `version` + :ruby_version, + # `engine_version` is the Jruby version or for MRI it is the same as `ruby_version` + # i.e. `..` + :engine_version + include LanguagePack::ShellHelpers def initialize(bundler_output, app = {}) @@ -74,7 +90,7 @@ def rake_is_vendored? end def default? - @version == none + !set end # determine if we're using jruby @@ -98,6 +114,18 @@ def vendored_bundler? false end + def major + @ruby_version.split(".")[0].to_i + end + + def minor + @ruby_version.split(".")[1].to_i + end + + def patch + @ruby_version.split(".")[2].to_i + end + # Returns the next logical version in the minor series # for example if the current ruby version is # `ruby-2.3.1` then then `next_logical_version(1)` @@ -126,21 +154,10 @@ def next_major_version(increment = 1) end private - - def none - if @app[:is_new] - DEFAULT_VERSION - elsif @app[:last_version] - @app[:last_version] - else - LEGACY_VERSION - end - end - def set_version if @bundler_output.empty? @set = false - @version = none + @version = @app[:last_version] || DEFAULT_VERSION else @set = :gemfile @version = @bundler_output diff --git a/spec/hatchet/ruby_spec.rb b/spec/hatchet/ruby_spec.rb index 580219577..ca60cce2b 100644 --- a/spec/hatchet/ruby_spec.rb +++ b/spec/hatchet/ruby_spec.rb @@ -145,7 +145,8 @@ it "works" do buildpacks = [ :default, - "https://github.com/sharpstone/force_absolute_paths_buildpack" + "https://github.com/sharpstone/force_absolute_paths_buildpack", + "https://github.com/heroku/heroku-buildpack-inline.git", ] config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} @@ -189,12 +190,47 @@ dir = Pathname("client") dir.mkpath FileUtils.touch(dir.join(".gitkeep")) + + # Inline buildpack to ensure build_report file is emitted on compile + bin = Pathname("bin").tap(&:mkpath) + detect = bin.join("detect") + compile = bin.join("compile") + release = bin.join("release") + + [detect, compile, release].each do |path| + FileUtils.touch(path) + FileUtils.chmod("+x", path) + path.write(<<~EOF) + #!/usr/bin/env bash + exit 0 + EOF + end + + compile.write(<<~EOF) + #!/usr/bin/env bash + REPORT_FILE="${CACHE_DIR}/.heroku/ruby/build_report.yml" + echo "## PRINTING REPORT FILE ##" + #{Pathname(__dir__).join("..").join("..").join("bin").join("report").read} + echo "## REPORT FILE DONE ##" + EOF end app.deploy do |app| expected = "3.3.1" expect(expected).to_not eq(LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER) expect(app.output).to match("cd version ruby #{expected}") + begin + report_match = app.output.match(/## PRINTING REPORT FILE ##(?.*)## REPORT FILE DONE/m) # https://rubular.com/r/FfaV5AEstigaMO + expect(report_match).to be_truthy + yaml = report_match[:yaml].gsub(/remote: /, "") + report = YAML.load(yaml) + expect(report.fetch("ruby.version")).to eq(expected) + rescue Exception => e + puts app.output + puts yaml if yaml + puts report.inspect if report + raise e + end expect(app.run("which ruby").strip).to eq("/app/bin/ruby") end diff --git a/spec/helpers/bundler_wrapper_spec.rb b/spec/helpers/bundler_wrapper_spec.rb index cf0362d2b..b90c2c2f9 100644 --- a/spec/helpers/bundler_wrapper_spec.rb +++ b/spec/helpers/bundler_wrapper_spec.rb @@ -54,9 +54,23 @@ Dir.mktmpdir do |dir| gemfile = Pathname(dir).join("Gemfile") lockfile = Pathname(dir).join("Gemfile.lock").tap {|p| p.write("BUNDLED WITH\n 2.5.7") } + report = HerokuBuildReport.dev_null - bundler = LanguagePack::Helpers::BundlerWrapper.new(gemfile_path: gemfile) + bundler = LanguagePack::Helpers::BundlerWrapper.new( + gemfile_path: gemfile, + report: report + ) expect(bundler.supports_multiple_platforms?).to be_truthy + + expect(report.data).to eq( + { + "bundled_with" => "2.5.7", + "bundler_major" => "2", + "bundler_minor" => "5", + "bundler_patch" => "23", + "bundler_version_installed" => "2.5.23", + } + ) end end end diff --git a/spec/helpers/heroku_build_report_spec.rb b/spec/helpers/heroku_build_report_spec.rb new file mode 100644 index 000000000..432ae8912 --- /dev/null +++ b/spec/helpers/heroku_build_report_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe "Build report" do + it "writes valid yaml" do + Dir.mktmpdir do |dir| + path = Pathname(dir).join(".report.yml") + report = HerokuBuildReport::YamlReport.new( + path: path + ) + report.capture( + "string" => "'with single quotes'", + "string_plain" => "plain", + "number" => 22, + "boolean" => true, + ) + + expect(path.read).to eq(<<~EOF) + --- + string: "'with single quotes'" + string_plain: plain + number: 22 + boolean: true + EOF + end + end +end diff --git a/spec/installers/heroku_ruby_installer_spec.rb b/spec/installers/heroku_ruby_installer_spec.rb index faf0f87e5..fa6c10d11 100644 --- a/spec/installers/heroku_ruby_installer_spec.rb +++ b/spec/installers/heroku_ruby_installer_spec.rb @@ -1,14 +1,18 @@ require "spec_helper" describe LanguagePack::Installers::HerokuRubyInstaller do - let(:installer) { + def installer(report: HerokuBuildReport::GLOBAL) LanguagePack::Installers::HerokuRubyInstaller.new( multi_arch_stacks: [], stack: "cedar-14", arch: nil, + report: report ) - } - let(:ruby_version) { LanguagePack::RubyVersion.new("ruby-2.3.3") } + end + + def ruby_version + LanguagePack::RubyVersion.new("ruby-2.3.3") + end describe "#fetch_unpack" do it "should fetch and unpack mri" do @@ -26,11 +30,44 @@ it "should install ruby and setup binstubs" do Dir.mktmpdir do |dir| Dir.chdir(dir) do - installer.install(ruby_version, "#{dir}/vendor/ruby") + report = HerokuBuildReport.dev_null + installer(report: report).install(ruby_version, "#{dir}/vendor/ruby") expect(File.symlink?("#{dir}/bin/ruby")).to be true expect(File.symlink?("#{dir}/bin/ruby.exe")).to be true expect(File).to exist("#{dir}/vendor/ruby/bin/ruby") + + expect(report.data["ruby.version"]).to eq("2.3.3") + expect(report.data["ruby.engine"]).to eq(:ruby) + expect(report.data["ruby.engine.version"]).to eq(report.data["ruby.version"]) + expect(report.data["ruby.major"]).to eq(2) + expect(report.data["ruby.minor"]).to eq(3) + expect(report.data["ruby.patch"]).to eq(3) + end + end + end + + it "should report jruby correctly" do + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + report = HerokuBuildReport.dev_null + + LanguagePack::Installers::HerokuRubyInstaller.new( + multi_arch_stacks: ["heroku-24"], + stack: "heroku-24", + arch: "arm64", + report: report + ).install( + LanguagePack::RubyVersion.new("ruby-3.1.4-p0-jruby-9.4.9.0"), + "#{dir}/vendor/ruby" + ) + + expect(report.data["ruby.version"]).to eq("3.1.4") + expect(report.data["ruby.engine"]).to eq(:jruby) + expect(report.data["ruby.engine.version"]).to eq("9.4.9.0") + expect(report.data["ruby.major"]).to eq(3) + expect(report.data["ruby.minor"]).to eq(1) + expect(report.data["ruby.patch"]).to eq(4) end end end From c4e930da2edadc2498f54b37e6309f2a27aa0d26 Mon Sep 17 00:00:00 2001 From: "heroku-linguist[bot]" <136119646+heroku-linguist[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:17:07 +0000 Subject: [PATCH 4/7] Prepare release v298 (#1573) Co-authored-by: heroku-linguist[bot] <136119646+heroku-linguist[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 +++++- lib/language_pack/version.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c12801703..a7ee9e122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] + +## [v298] - 2025-04-03 + - Added observability metrics (https://github.com/heroku/heroku-buildpack-ruby/pull/1569) ## [v297] - 2025-03-26 @@ -1656,7 +1659,8 @@ Bugfixes: * Change gem detection to use lockfile parser * use `$RACK_ENV` when thin is detected for rack apps -[unreleased]: https://github.com/heroku/heroku-buildpack-ruby/compare/v297...main +[unreleased]: https://github.com/heroku/heroku-buildpack-ruby/compare/v298...main +[v298]: https://github.com/heroku/heroku-buildpack-ruby/compare/v297...v298 [v297]: https://github.com/heroku/heroku-buildpack-ruby/compare/v296...v297 [v296]: https://github.com/heroku/heroku-buildpack-ruby/compare/v295...v296 [v295]: https://github.com/heroku/heroku-buildpack-ruby/compare/v294...v295 diff --git a/lib/language_pack/version.rb b/lib/language_pack/version.rb index e969be762..15bb256f1 100644 --- a/lib/language_pack/version.rb +++ b/lib/language_pack/version.rb @@ -2,6 +2,6 @@ module LanguagePack class LanguagePack::Base - BUILDPACK_VERSION = "v297" + BUILDPACK_VERSION = "v298" end end From e6ce491c65a69e39563da5c15c574397f88f4a02 Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Fri, 4 Apr 2025 14:52:38 -0500 Subject: [PATCH 5/7] Ensure flat YAML build report (#1574) The `bin/report` structure is flat. All keys must map to values that are un-nested. Ruby's `to_yaml` will happily serialize ruby objects: ``` irb(main):004> puts Gem::Version.new("3.4.2").to_yaml --- !ruby/object:Gem::Version version: 3.4.2 ``` This commit fixes this problem by inspecting if the value being serialized is an object or not. If it is, it is then converted into a string. --- lib/heroku_build_report.rb | 9 +++++++++ spec/helpers/heroku_build_report_spec.rb | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/heroku_build_report.rb b/lib/heroku_build_report.rb index 1fa2e6229..f855b24b8 100644 --- a/lib/heroku_build_report.rb +++ b/lib/heroku_build_report.rb @@ -27,6 +27,10 @@ def clear! @path.write("") end + def complex_object?(value) + value.to_yaml.match?(/!ruby\/object:/) + end + def capture(metrics = {}) metrics.each do |(key, value)| return if key.nil? || key.to_s.strip.empty? @@ -34,6 +38,11 @@ def capture(metrics = {}) key = key&.strip raise "Key cannot be empty (#{key.inspect} => #{value})" if key.nil? || key.empty? + # Don't serialize complex values by accident + if complex_object?(value) + value = value.to_s + end + @data["#{key}"] = value end diff --git a/spec/helpers/heroku_build_report_spec.rb b/spec/helpers/heroku_build_report_spec.rb index 432ae8912..069da1cbc 100644 --- a/spec/helpers/heroku_build_report_spec.rb +++ b/spec/helpers/heroku_build_report_spec.rb @@ -1,6 +1,25 @@ require 'spec_helper' describe "Build report" do + it "handles complex object serialization by converting them to strings" do + Dir.mktmpdir do |dir| + path = Pathname(dir).join(".report.yml") + report = HerokuBuildReport::YamlReport.new( + path: path + ) + value = Gem::Version.new("3.4.2") + expect(report.complex_object?(value)).to eq(true) + expect(value.to_yaml).to_not eq(value.to_s.to_yaml) + report.capture("key" => value) + + expect(report.data).to eq({"key" => "3.4.2"}) + expect(path.read).to eq(<<~EOF) + --- + key: 3.4.2 + EOF + end + end + it "writes valid yaml" do Dir.mktmpdir do |dir| path = Pathname(dir).join(".report.yml") From 971929593fff87a8cf85eb900743a18bc22cae23 Mon Sep 17 00:00:00 2001 From: "heroku-linguist[bot]" <136119646+heroku-linguist[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:33:43 +0000 Subject: [PATCH 6/7] Prepare release v299 (#1575) Co-authored-by: heroku-linguist[bot] <136119646+heroku-linguist[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 +++++- lib/language_pack/version.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ee9e122..99b0138b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## [Unreleased] +## [v299] - 2025-04-07 + + ## [v298] - 2025-04-03 - Added observability metrics (https://github.com/heroku/heroku-buildpack-ruby/pull/1569) @@ -1659,7 +1662,8 @@ Bugfixes: * Change gem detection to use lockfile parser * use `$RACK_ENV` when thin is detected for rack apps -[unreleased]: https://github.com/heroku/heroku-buildpack-ruby/compare/v298...main +[unreleased]: https://github.com/heroku/heroku-buildpack-ruby/compare/v299...main +[v299]: https://github.com/heroku/heroku-buildpack-ruby/compare/v298...v299 [v298]: https://github.com/heroku/heroku-buildpack-ruby/compare/v297...v298 [v297]: https://github.com/heroku/heroku-buildpack-ruby/compare/v296...v297 [v296]: https://github.com/heroku/heroku-buildpack-ruby/compare/v295...v296 diff --git a/lib/language_pack/version.rb b/lib/language_pack/version.rb index 15bb256f1..7b044dcac 100644 --- a/lib/language_pack/version.rb +++ b/lib/language_pack/version.rb @@ -2,6 +2,6 @@ module LanguagePack class LanguagePack::Base - BUILDPACK_VERSION = "v298" + BUILDPACK_VERSION = "v299" end end From fe6bd0556afa3b07fde421084afe3558e500a93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20KUBLER?= Date: Wed, 9 Apr 2025 11:20:32 +0200 Subject: [PATCH 7/7] chore: fix report path and remove specs --- bin/report | 2 +- bin/support/ruby_compile | 2 +- spec/fixtures/invalid_encoding.log | 1 - spec/fixtures/windows_lockfile/Gemfile | 0 spec/fixtures/windows_lockfile/Gemfile.lock | 2 - spec/hatchet/bugs_spec.rb | 28 -- spec/hatchet/buildpack_spec.rb | 14 - spec/hatchet/bundler_spec.rb | 34 -- spec/hatchet/ci_spec.rb | 76 ---- spec/hatchet/getting_started_spec.rb | 15 - spec/hatchet/node_spec.rb | 45 --- spec/hatchet/rails23_spec.rb | 13 - spec/hatchet/rails3_spec.rb | 29 -- spec/hatchet/rails4_spec.rb | 54 --- spec/hatchet/rails6_spec.rb | 29 -- spec/hatchet/rails7_spec.rb | 26 -- spec/hatchet/rails8_spec.rb | 4 - spec/hatchet/rubies_spec.rb | 70 ---- spec/hatchet/ruby_spec.rb | 358 ------------------ spec/helpers/binstub_check_spec.rb | 89 ----- spec/helpers/bundler_wrapper_spec.rb | 138 ------- spec/helpers/config_spec.rb | 17 - spec/helpers/download_presence_spec.rb | 137 ------- spec/helpers/fetcher_spec.rb | 20 - spec/helpers/heroku_build_report_spec.rb | 45 --- spec/helpers/node_installer_spec.rb | 18 - spec/helpers/outdated_ruby_version_spec.rb | 135 ------- spec/helpers/rails_runner_spec.rb | 140 ------- spec/helpers/rake_runner_spec.rb | 50 --- spec/helpers/ruby_version_spec.rb | 160 -------- spec/helpers/shell_spec.rb | 97 ----- spec/helpers/stale_file_cleaner_spec.rb | 39 -- spec/helpers/yarn_installer_spec.rb | 19 - spec/installers/heroku_ruby_installer_spec.rb | 75 ---- spec/rake/deploy_check_spec.rb | 150 -------- spec/spec_helper.rb | 114 ------ spec/unit/bash_functions_spec.rb | 123 ------ 37 files changed, 2 insertions(+), 2366 deletions(-) delete mode 100644 spec/fixtures/invalid_encoding.log delete mode 100644 spec/fixtures/windows_lockfile/Gemfile delete mode 100644 spec/fixtures/windows_lockfile/Gemfile.lock delete mode 100644 spec/hatchet/bugs_spec.rb delete mode 100644 spec/hatchet/buildpack_spec.rb delete mode 100644 spec/hatchet/bundler_spec.rb delete mode 100644 spec/hatchet/ci_spec.rb delete mode 100644 spec/hatchet/getting_started_spec.rb delete mode 100644 spec/hatchet/node_spec.rb delete mode 100644 spec/hatchet/rails23_spec.rb delete mode 100644 spec/hatchet/rails3_spec.rb delete mode 100644 spec/hatchet/rails4_spec.rb delete mode 100644 spec/hatchet/rails6_spec.rb delete mode 100644 spec/hatchet/rails7_spec.rb delete mode 100644 spec/hatchet/rails8_spec.rb delete mode 100644 spec/hatchet/rubies_spec.rb delete mode 100644 spec/hatchet/ruby_spec.rb delete mode 100644 spec/helpers/binstub_check_spec.rb delete mode 100644 spec/helpers/bundler_wrapper_spec.rb delete mode 100644 spec/helpers/config_spec.rb delete mode 100644 spec/helpers/download_presence_spec.rb delete mode 100644 spec/helpers/fetcher_spec.rb delete mode 100644 spec/helpers/heroku_build_report_spec.rb delete mode 100644 spec/helpers/node_installer_spec.rb delete mode 100644 spec/helpers/outdated_ruby_version_spec.rb delete mode 100644 spec/helpers/rails_runner_spec.rb delete mode 100644 spec/helpers/rake_runner_spec.rb delete mode 100644 spec/helpers/ruby_version_spec.rb delete mode 100644 spec/helpers/shell_spec.rb delete mode 100644 spec/helpers/stale_file_cleaner_spec.rb delete mode 100644 spec/helpers/yarn_installer_spec.rb delete mode 100644 spec/installers/heroku_ruby_installer_spec.rb delete mode 100644 spec/rake/deploy_check_spec.rb delete mode 100644 spec/spec_helper.rb delete mode 100644 spec/unit/bash_functions_spec.rb diff --git a/bin/report b/bin/report index 7e7a33331..2a0c3730f 100755 --- a/bin/report +++ b/bin/report @@ -3,7 +3,7 @@ set -euo pipefail CACHE_DIR="${2}" -REPORT_FILE="${CACHE_DIR}/.heroku/ruby/build_report.yml" +REPORT_FILE="${CACHE_DIR}/.scalingo/ruby/build_report.yml" # Whilst the release file is always written by the buildpack, some apps use # third-party slug cleaner buildpacks to remove this and other files, so we diff --git a/bin/support/ruby_compile b/bin/support/ruby_compile index 83c2c39f4..b014b04de 100755 --- a/bin/support/ruby_compile +++ b/bin/support/ruby_compile @@ -13,7 +13,7 @@ require "language_pack/shell_helpers" HerokuBuildReport.set_global( # Coupled with `bin/report` path: Pathname(ARGV[1]) - .join(".heroku") + .join(".scalingo") .join("ruby") .join("build_report.yml") ).tap(&:clear!) diff --git a/spec/fixtures/invalid_encoding.log b/spec/fixtures/invalid_encoding.log deleted file mode 100644 index 16047e6e6..000000000 --- a/spec/fixtures/invalid_encoding.log +++ /dev/null @@ -1 +0,0 @@ -ACENOCUMAROL,AZECAR,1 MG 30 COMPRIMIDOS, ññóénñ diff --git a/spec/fixtures/windows_lockfile/Gemfile b/spec/fixtures/windows_lockfile/Gemfile deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/fixtures/windows_lockfile/Gemfile.lock b/spec/fixtures/windows_lockfile/Gemfile.lock deleted file mode 100644 index 4d0fc2948..000000000 --- a/spec/fixtures/windows_lockfile/Gemfile.lock +++ /dev/null @@ -1,2 +0,0 @@ -BUNDLED WITH - 2.0.2 diff --git a/spec/hatchet/bugs_spec.rb b/spec/hatchet/bugs_spec.rb deleted file mode 100644 index f1ca04ca0..000000000 --- a/spec/hatchet/bugs_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require_relative '../spec_helper' - -describe "Bugs" do - context "database connections" do - it "fails with better error message" do - Hatchet::Runner.new("ruby-getting-started", allow_failure: true).tap do |app| - app.before_deploy do - Pathname("Rakefile").write(<<~EOM) - require 'bundler' - Bundler.require(:default) - - require 'active_record' - - task "assets:precompile" do - # Try to connect to a database that doesn't exist yet - ActiveRecord::Base.establish_connection - ActiveRecord::Base.connection.execute("") - end - EOM - end - - app.deploy do - expect(app.output).to match("https://devcenter.heroku.com/articles/pre-provision-database") - end - end - end - end -end diff --git a/spec/hatchet/buildpack_spec.rb b/spec/hatchet/buildpack_spec.rb deleted file mode 100644 index 9cfa4126d..000000000 --- a/spec/hatchet/buildpack_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require_relative '../spec_helper' - -describe "Buildpack internals" do - it "handles PATH with a newline in it correctly" do - buildpacks = [ - "https://github.com/sharpstone/export_path_with_newlines_buildpack", - :default, - "https://github.com/heroku/null-buildpack" - ] - Hatchet::Runner.new("default_ruby", buildpacks: buildpacks).deploy do |app| - expect(app.output).to_not match("No such file or directory") - end - end -end diff --git a/spec/hatchet/bundler_spec.rb b/spec/hatchet/bundler_spec.rb deleted file mode 100644 index 74184e01e..000000000 --- a/spec/hatchet/bundler_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe "Bundler" do - it "can be configured with BUNDLE_WITHOUT env var with spaces in it" do - Hatchet::Runner.new("default_ruby", config: {"BUNDLE_WITHOUT" => "foo bar baz"}).tap do |app| - app.deploy do - expect(app.output).to match("BUNDLE_WITHOUT='foo:bar:baz'") - expect(app.output).to match("Your BUNDLE_WITHOUT contains a space") - end - end - end - - it "deploys with version 1.x" do - pending("Must enable HATCHET_EXPENSIVE_MODE") unless ENV["HATCHET_EXPENSIVE_MODE"] - - Hatchet::Runner.new("default_ruby").tap do |app| - app.before_deploy do - set_bundler_version(version: "1.17.3") - Pathname("Gemfile.lock").write(<<~EOF, mode: "a") - - RUBY VERSION - ruby 3.1.6 - EOF - end - app.deploy do - expect(app.output).to match("Deprecating bundler 1.17.3") - - app.run("which -a rake") do |which_rake| - expect(which_rake).to include("/app/vendor/bundle/bin/rake") - end - end - end - end -end diff --git a/spec/hatchet/ci_spec.rb b/spec/hatchet/ci_spec.rb deleted file mode 100644 index 599fe372f..000000000 --- a/spec/hatchet/ci_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'spec_helper' - -describe "CI" do - it "Does not cause the double ruby rainbow bug" do - Hatchet::Runner.new("heroku-ci-json-example").run_ci do |test_run| - expect(test_run.status).to eq(:succeeded) - - install_bundler_count = test_run.output.scan("Installing bundler").count - expect(install_bundler_count).to eq(1), "Expected output to only install bundler once but was found #{install_bundler_count} times. output:\n#{test_run.output}" - end - end - - it "Works with Rails: ruby schema apps" do - Hatchet::Runner.new("rails_8_ruby_schema", stack: "heroku-24").tap do |app| - app.before_deploy do - Pathname("app.json").write(<<~EOF) - { - "environments": { - "test": { - "addons":[ - "heroku-postgresql:in-dyno" - ] - } - } - } - EOF - end - - app.run_ci do |test_run| - expect(test_run.output).to match("db:schema:load completed") - end - end - end - - it "Works with Rails: SQL schema apps" do - Hatchet::Runner.new("rails_8_sql_schema", stack: "heroku-24").tap do |app| - app.before_deploy do - Pathname("app.json").write(<<~EOF) - { - "environments": { - "test": { - "addons":[ - "heroku-postgresql:in-dyno" - ] - } - } - } - EOF - end - - app.run_ci do |test_run| - expect(test_run.output).to match("db:schema:load completed") - end - end - end - - it "Works with a vanilla ruby app" do - Hatchet::Runner.new("ruby_no_rails_test").run_ci do |test_run| - # Test no whitespace in front of output - expect(test_run.output).to_not match(/^ +Finished in/) - expect(test_run.output).to match(/^Finished in/) - end - end - - it "Uses the cache" do - runner = Hatchet::Runner.new("ruby_no_rails_test") - runner.run_ci do |test_run| - fetching_rake = "Fetching rake" - expect(test_run.output).to match(fetching_rake) - - test_run.run_again - - expect(test_run.output).to_not match(fetching_rake) - end - end -end diff --git a/spec/hatchet/getting_started_spec.rb b/spec/hatchet/getting_started_spec.rb deleted file mode 100644 index f31bad042..000000000 --- a/spec/hatchet/getting_started_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require_relative '../spec_helper' - -describe "Heroku ruby getting started" do - it "clears runtime cache" do - Hatchet::Runner.new("ruby-getting-started").deploy do |app| - expect(app.run("ls tmp/cache/assets")).to_not match("sprockets") - end - end - - it "works on Heroku-24" do - Hatchet::Runner.new("ruby-getting-started", stack: "heroku-24").deploy do |app| - expect(app.run("which ruby").strip).to eq("/app/bin/ruby") - end - end -end diff --git a/spec/hatchet/node_spec.rb b/spec/hatchet/node_spec.rb deleted file mode 100644 index fe816dc27..000000000 --- a/spec/hatchet/node_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' - -describe "Node and Yarn" do - it "works without the node buildpack" do - buildpacks = [ - :default, - "https://github.com/sharpstone/force_absolute_paths_buildpack" - ] - config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} - - Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks, config: config).deploy do |app, heroku| - # https://rubular.com/r/4bkL8fYFTQwt0Q - expect(app.output).to match(/vendor\/yarn-v\d+\.\d+\.\d+\/bin\/yarn is the yarn directory/) - expect(app.output).to_not include(".heroku/yarn/bin/yarn is the yarn directory") - - expect(app.output).to include("bin/node is the node directory") - expect(app.output).to_not include(".heroku/node/bin/node is the node directory") - - expect(app.output).to include("Installing a default version (#{LanguagePack::Helpers::Nodebin::YARN_VERSION}) of Yarn") - expect(app.output).to include("Installing a default version (#{LanguagePack::Helpers::Nodebin::NODE_VERSION}) of Node.js") - - expect(app.run("which node")).to match("/app/bin/node") # We put node in bin/node - expect(app.run("which yarn")).to match("/app/vendor/yarn-") # We put yarn in /app/vendor/yarn- - end - end - - it "works with the node buildpack" do - buildpacks = [ - "heroku/nodejs", - :default, - "https://github.com/sharpstone/force_absolute_paths_buildpack" - ] - config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} - - Hatchet::Runner.new("minimal_webpacker", buildpacks: buildpacks, config: config).deploy do |app, heroku| - expect(app.output).to include("yarn install") - expect(app.output).to include(".heroku/yarn/bin/yarn is the yarn directory") - expect(app.output).to include(".heroku/node/bin/node is the node directory") - - expect(app.run("which node")).to match("/app/.heroku/node/bin") - expect(app.run("which yarn")).to match("/app/.heroku/yarn/bin") - end - end -end - diff --git a/spec/hatchet/rails23_spec.rb b/spec/hatchet/rails23_spec.rb deleted file mode 100644 index 87e5eee51..000000000 --- a/spec/hatchet/rails23_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 2.3.x" do - it "should deploy" do - skip("Need RAILS_LTS_CREDS env var set") unless ENV["RAILS_LTS_CREDS"] - - Hatchet::Runner.new('rails_lts_23_default_ruby', config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.deploy do - # assert deploy is successful - end - end - end -end diff --git a/spec/hatchet/rails3_spec.rb b/spec/hatchet/rails3_spec.rb deleted file mode 100644 index 3450d9a79..000000000 --- a/spec/hatchet/rails3_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 3.x" do - it "should deploy and inject plugins" do - skip("Need RAILS_LTS_CREDS env var set") unless ENV["RAILS_LTS_CREDS"] - - Hatchet::Runner.new("rails3_default_ruby", config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.before_deploy do - set_lts_ruby_version - set_bundler_version(version: :default) - end - - app.deploy do - # Rails 3 doesn't work with Postgres 8+ out of the box and Rails - # LTS hasn't patched this yet. We're skipping asset compilation for now - # by deleting the Rakefile - # - # expect(app.output).to include("Asset precompilation completed") - - expect(app.output).to match("WARNING") - expect(app.output).to match("Add 'rails_12factor' gem to your Gemfile to skip plugin injection") - - ls = app.run("ls vendor/plugins") - expect(ls).to match("rails3_serve_static_assets") - expect(ls).to match("rails_log_stdout") - end - end - end -end diff --git a/spec/hatchet/rails4_spec.rb b/spec/hatchet/rails4_spec.rb deleted file mode 100644 index d29633cb1..000000000 --- a/spec/hatchet/rails4_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 4.x" do - it "should be able to run a migration without heroku specific database.yml" do - skip("Need RAILS_LTS_CREDS env var set") unless ENV["RAILS_LTS_CREDS"] - - Hatchet::Runner.new("rails42_default_ruby", config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.before_deploy do - set_lts_ruby_version - set_bundler_version(version: :default) - end - app.deploy do - # it Don't over-write database.yml - expect(app.output).not_to include("Writing config/database.yml to read from DATABASE_URL") - - # it sets RAILS_SERVE_STATIC_FILES env var - output = app.run("rails runner 'puts ENV[%Q{RAILS_SERVE_STATIC_FILES}].present?'") - expect(output).to match(/true/) - end - end - end - - it "should skip asset compilation when deployed with NEW manifest file" do - skip("Need RAILS_LTS_CREDS env var set") unless ENV["RAILS_LTS_CREDS"] - - Hatchet::Runner.new("rails42_default_ruby", config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.before_deploy do - set_lts_ruby_version - set_bundler_version(version: :default) - Pathname("public/assets/manifest-ccf61eade4793995271564a4767ce6b6.json").tap {|p| p.dirname.mkpath; FileUtils.touch(p) } - end - - app.deploy do - expect(app.output).to include("Detected manifest file, assuming assets were compiled locally") - end - end - end - - it "should skip asset compilation when deployed with OLD manifest file" do - skip("Need RAILS_LTS_CREDS env var set") unless ENV["RAILS_LTS_CREDS"] - - Hatchet::Runner.new("rails42_default_ruby", config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.before_deploy do - set_lts_ruby_version - set_bundler_version(version: :default) - Pathname("public/assets/.sprockets-manifest-040763ccc5036260c52c6adcf77d73f7.json").tap {|p| p.dirname.mkpath; FileUtils.touch(p) } - end - - app.deploy do - expect(app.output).to include("Detected manifest file, assuming assets were compiled locally") - end - end - end -end diff --git a/spec/hatchet/rails6_spec.rb b/spec/hatchet/rails6_spec.rb deleted file mode 100644 index 9373d1fa8..000000000 --- a/spec/hatchet/rails6_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 6" do - it "should detect successfully" do - Hatchet::App.new('rails61').in_directory_fork do - expect(LanguagePack::Rails5.use?).to eq(false) - expect(LanguagePack::Rails6.use?).to eq(true) - end - end - - it "deploys and serves web requests via puma" do - before_deploy = Proc.new do - run! "echo 'web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}' > Procfile" - - # Test Clean task does not get called if it does not exist - # This file will only have the `assets:precompile` task in it, but not `assets:clean` - run! %Q{echo 'task "assets:precompile" do ; end' > Rakefile} - end - - Hatchet::Runner.new('rails61', before_deploy: before_deploy, config: rails_lts_config, stack: rails_lts_stack).deploy do |app| - expect(app.output).to match("Fetching railties 6") - - expect(app.output).to match("rake assets:precompile") - expect(app.output).to_not match("rake assets:clean") - - expect(web_boot_status(app)).to_not eq("crashed") - end - end -end diff --git a/spec/hatchet/rails7_spec.rb b/spec/hatchet/rails7_spec.rb deleted file mode 100644 index 1cea712e2..000000000 --- a/spec/hatchet/rails7_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 7" do - it "should detect successfully" do - Hatchet::App.new('rails-jsbundling').in_directory_fork do - expect(LanguagePack::Rails6.use?).to eq(false) - expect(LanguagePack::Rails7.use?).to eq(true) - end - end - - it "works with jsbundling" do - Hatchet::Runner.new("rails-jsbundling").tap do |app| - app.deploy do - expect(app.output).to include("yarn install") - expect(app.output).to include("Asset precompilation completed") - end - end - end - - it "Works on Heroku CI" do - Hatchet::Runner.new("rails-jsbundling").run_ci do |test_run| - expect(test_run.output).to match("db:schema:load") - expect(test_run.output).to match("db:migrate") - end - end -end diff --git a/spec/hatchet/rails8_spec.rb b/spec/hatchet/rails8_spec.rb deleted file mode 100644 index 92afaf574..000000000 --- a/spec/hatchet/rails8_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require_relative '../spec_helper' - -describe "Rails 8" do -end diff --git a/spec/hatchet/rubies_spec.rb b/spec/hatchet/rubies_spec.rb deleted file mode 100644 index 402ee4ef2..000000000 --- a/spec/hatchet/rubies_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require_relative '../spec_helper' - -describe "Ruby versions" do - it "should deploy jdk on heroku-24" do - Hatchet::Runner.new("default_ruby", stack: "heroku-24").tap do |app| - app.before_deploy do |app| - Pathname("Gemfile.lock").write(<<~EOM) - GEM - remote: https://rubygems.org/ - specs: - rack (3.1.8) - rake (13.2.1) - webrick (1.9.1) - - PLATFORMS - java - - DEPENDENCIES - rack - rake - webrick - - RUBY VERSION - ruby 3.1.4p0 (jruby 9.4.8.0) - - BUNDLED WITH - 2.5.23 - EOM - - Pathname("Rakefile").write(<<~'EOM') - task "assets:precompile" do - puts "JRUBY_OPTS is: #{ENV['JRUBY_OPTS']}" - end - EOM - end - - app.deploy do - expect(app.output).to match("JRUBY_OPTS is: -Xcompile.invokedynamic=false") - - app.set_config("JRUBY_BUILD_OPTS" => "--dev") - app.commit! - app.push! - expect(app.output).to match("JRUBY_OPTS is: --dev") - - expect(app.run("ruby -v")).to match("jruby") - end - end - end -end - -describe "Upgrading ruby apps" do - it "works when changing versions" do - version = "3.3.1" - expect(version).to_not eq(LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER) - app = Hatchet::Runner.new("default_ruby", stack: DEFAULT_STACK) - app.deploy do |app| - # default version - expect(app.run("env | grep MALLOC_ARENA_MAX")).to match("MALLOC_ARENA_MAX=2") - expect(app.run("env | grep DISABLE_SPRING")).to match("DISABLE_SPRING=1") - - # Deploy again - run!(%Q{echo "ruby '#{version}'" >> Gemfile}) - run!("git add -A; git commit -m update-ruby") - app.push! - expect(app.output).to match(version) - expect(app.run("ruby -v")).to match(version) - expect(app.output).to match("Ruby version change detected") - end - end -end diff --git a/spec/hatchet/ruby_spec.rb b/spec/hatchet/ruby_spec.rb deleted file mode 100644 index ca60cce2b..000000000 --- a/spec/hatchet/ruby_spec.rb +++ /dev/null @@ -1,358 +0,0 @@ -require_relative '../spec_helper' - -describe "Ruby apps" do - describe "with mingw platform" do - it "is detected as a Windows app" do - Hatchet::Runner.new("default_ruby").tap do |app| - app.before_deploy do - Pathname("Gemfile").write(<<~'EOF') - source "https://rubygems.org" - - gem "rake" - EOF - - Pathname("Gemfile.lock").write(<<~'EOF') - GEM - remote: https://rubygems.org/ - specs: - rake (13.2.1) - - PLATFORMS - x86-mingw32 - ruby - - DEPENDENCIES - rake - - BUNDLED WITH - 2.5.9 - EOF - end - - app.deploy do - expect(app.output).to include("Windows platform detected, preserving `Gemfile.lock`") - end - end - end - end - - # https://github.com/heroku/heroku-buildpack-ruby/issues/1025 - describe "bin/rake binstub" do - it "loads git gems at build time when executing `rake`" do - Hatchet::Runner.new("git_gemspec").tap do |app| - app.before_deploy do - File.open("Rakefile", "w+") do |f| - f.puts(<<~EOF) - task "assets:precompile" do - require 'mini_histogram' - puts "successfully loaded git gem" - end - EOF - end - end - app.deploy do - expect(app.output).to match("successfully loaded git gem") - expect(app.run("rake assets:precompile")).to match("successfully loaded git gem") - end - end - end - - it "loads bundler into memory" do - Hatchet::Runner.new("default_ruby").tap do |app| - app.before_deploy do - File.open("Rakefile", "w+") do |f| - f.puts(<<~EOF) - task "assets:precompile" do - puts Bundler.methods - - puts "bundler loaded in rake context" - end - EOF - end - end - app.deploy do - expect(app.output).to match("bundler loaded in rake context") - expect(app.run("rake assets:precompile")).to match("bundler loaded in rake context") - end - end - end - - it "loads custom rake binstub" do - Hatchet::Runner.new("default_ruby").tap do |app| - app.before_deploy do - FileUtils.mkdir_p("bin") - - File.open("bin/rake", "w+") do |f| - f.puts(<<~EOF) - #!/usr/bin/env ruby - - puts "rake assets:precompile" # Needed to trigger the `rake -P` task detection - puts "custom rake binstub called" - EOF - end - FileUtils.chmod("+x", "bin/rake") - end - app.deploy do - expect(app.output).to match("custom rake binstub called") - expect(app.run("rake")).to match("custom rake binstub called") - end - end - end - end - - describe "bad ruby version" do - it "gives a helpful error" do - Hatchet::Runner.new('ruby_version_does_not_exist', allow_failure: true, stack: DEFAULT_STACK).deploy do |app| - expect(app.output).to match("The Ruby version you are trying to install does not exist: ruby-2.9.0.lol") - end - end - end - - describe "exporting path" do - it "puts local bin dir in path" do - before_deploy = Proc.new do - FileUtils.mkdir_p("bin") - File.open("bin/bloop", "w+") do |f| - f.puts(<<~EOF) - #!/usr/bin/env bash - - echo "bloop" - EOF - end - FileUtils.chmod("+x", "bin/bloop") - - File.open("Rakefile", "a") do |f| - f.puts(<<~EOF) - task "run:bloop" do - puts `bloop` - raise "Could not bloop" unless $?.success? - end - EOF - end - end - buildpacks = [ - :default, - "https://github.com/schneems/buildpack-ruby-rake-deploy-tasks" - ] - config = { "DEPLOY_TASKS" => "run:bloop"} - Hatchet::Runner.new('default_ruby', stack: DEFAULT_STACK, buildpacks: buildpacks, config: config, before_deploy: before_deploy).deploy do |app| - expect(app.output).to match("bloop") - end - end - end - - describe "running Ruby from outside the default dir" do - it "works" do - buildpacks = [ - :default, - "https://github.com/sharpstone/force_absolute_paths_buildpack", - "https://github.com/heroku/heroku-buildpack-inline.git", - ] - config = {FORCE_ABSOLUTE_PATHS_BUILDPACK_IGNORE_PATHS: "BUNDLE_PATH"} - - - Hatchet::Runner.new('default_ruby', stack: DEFAULT_STACK, buildpacks: buildpacks, config: config).tap do |app| - app.before_deploy do - Pathname("Gemfile").write(<<~'EOF') - source "https://rubygems.org" - - gem "rake" - EOF - - Pathname("Gemfile.lock").write(<<~'EOF') - GEM - remote: https://rubygems.org/ - specs: - rake (13.0.6) - - PLATFORMS - ruby - x86_64-darwin-20 - - DEPENDENCIES - rake - - RUBY VERSION - ruby 3.3.1p0 - EOF - - Pathname("Rakefile").write(<<~'EOF') - task "assets:precompile" do - out = `cd client && bundle exec ruby -v` - puts "cd version #{out}" - unless $?.success? - puts "Failed: #{out}" - exit 1 - end - end - EOF - - dir = Pathname("client") - dir.mkpath - FileUtils.touch(dir.join(".gitkeep")) - - # Inline buildpack to ensure build_report file is emitted on compile - bin = Pathname("bin").tap(&:mkpath) - detect = bin.join("detect") - compile = bin.join("compile") - release = bin.join("release") - - [detect, compile, release].each do |path| - FileUtils.touch(path) - FileUtils.chmod("+x", path) - path.write(<<~EOF) - #!/usr/bin/env bash - exit 0 - EOF - end - - compile.write(<<~EOF) - #!/usr/bin/env bash - REPORT_FILE="${CACHE_DIR}/.heroku/ruby/build_report.yml" - echo "## PRINTING REPORT FILE ##" - #{Pathname(__dir__).join("..").join("..").join("bin").join("report").read} - echo "## REPORT FILE DONE ##" - EOF - end - - app.deploy do |app| - expected = "3.3.1" - expect(expected).to_not eq(LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER) - expect(app.output).to match("cd version ruby #{expected}") - begin - report_match = app.output.match(/## PRINTING REPORT FILE ##(?.*)## REPORT FILE DONE/m) # https://rubular.com/r/FfaV5AEstigaMO - expect(report_match).to be_truthy - yaml = report_match[:yaml].gsub(/remote: /, "") - report = YAML.load(yaml) - expect(report.fetch("ruby.version")).to eq(expected) - rescue Exception => e - puts app.output - puts yaml if yaml - puts report.inspect if report - raise e - end - - expect(app.run("which ruby").strip).to eq("/app/bin/ruby") - end - end - end - end - - describe "bundler ruby version matcher" do - it "installs a version even when not present in the Gemfile.lock" do - version = "3.3.1" - expect(version).to_not eq(LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER) - - Hatchet::Runner.new('default_ruby', stack: DEFAULT_STACK).tap do |app| - app.before_deploy do - Pathname("Gemfile").write(<<~EOF) - source "https://rubygems.org" - - ruby "#{version}" - - gem "sinatra" - EOF - - Pathname("Gemfile.lock").write(<<~'EOF') - GEM - remote: https://rubygems.org/ - specs: - mustermann (1.1.1) - ruby2_keywords (~> 0.0.1) - rack (2.2.3) - rack-protection (2.2.0) - rack - ruby2_keywords (0.0.5) - sinatra (2.2.0) - mustermann (~> 1.0) - rack (~> 2.2) - rack-protection (= 2.2.0) - tilt (~> 2.0) - tilt (2.0.10) - - PLATFORMS - ruby - x86_64-darwin-20 - - DEPENDENCIES - sinatra - EOF - - end - - app.deploy do |app| - # Intentionally different than the default ruby version - expect(app.output).to match("#{version}") - expect(app.run("ruby -v")).to match("#{version}") - end - end - end - end - - describe "database configuration" do - context "no active record" do - it "writes a heroku specific database.yml" do - Hatchet::Runner.new("default_ruby").deploy do |app, heroku| - expect(app.output).to include("Writing config/database.yml to read from DATABASE_URL") - expect(app.output).not_to include("Your app was upgraded to bundler") - expect(app.output).not_to include("Your Ruby version is not present on the next stack") - end - end - end - - context "active record 4.1+" do - it "doesn't write a heroku specific database.yml" do - Hatchet::Runner.new("rails61", config: rails_lts_config, stack: rails_lts_stack).tap do |app| - app.deploy do - expect(app.output).not_to include("Writing config/database.yml to read from DATABASE_URL") - end - end - end - end - end -end - -describe "Raise errors on specific gems" do - it "should raise on sqlite3" do - Hatchet::Runner.new("sqlite3_gemfile", allow_failure: true).deploy do |app| - expect(app).not_to be_deployed - expect(app.output).to include("Detected sqlite3 gem which is not supported") - expect(app.output).to include("devcenter.heroku.com/articles/sqlite3") - end - end -end - -describe "No Lockfile" do - it "should not deploy" do - Hatchet::Runner.new("no_lockfile", allow_failure: true).deploy do |app| - expect(app).not_to be_deployed - expect(app.output).to include("Gemfile.lock required") - end - end -end - -describe "Rack" do - it "should not overwrite already set environment variables" do - custom_env = "FFFUUUUUUU" - app = Hatchet::Runner.new("default_ruby", config: {"RACK_ENV" => custom_env}) - - app.deploy do |app| - expect(app.run("env")).to match(custom_env) - end - end -end - -describe "WEB_CONCURRENCY.sh" do - it "from a preceding buildpack is overwritten by this buildpack" do - buildpacks = [ - "heroku/nodejs", - :default - ] - before_deploy = -> { run!(%Q{echo "{}" > package.json}) } - Hatchet::Runner.new('default_ruby', stack: DEFAULT_STACK, buildpacks: buildpacks, before_deploy: before_deploy).deploy do |app| - expect(app.run("cat .profile.d/WEB_CONCURRENCY.sh").strip).to be_empty - expect(app.run("echo $WEB_CONCURRENCY").strip).to be_empty - expect(app.run("echo $WEB_CONCURRENCY", :heroku => {:env => "WEB_CONCURRENCY=0"}).strip).to eq("0") - end - end -end diff --git a/spec/helpers/binstub_check_spec.rb b/spec/helpers/binstub_check_spec.rb deleted file mode 100644 index 37d2e0c1f..000000000 --- a/spec/helpers/binstub_check_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require_relative "../spec_helper.rb" - -describe LanguagePack::Helpers::BinstubCheck do - def get_ruby_path! - out = `which ruby`.strip - raise "command `which ruby` failed with output: #{out}" unless $?.success? - - return Pathname.new(out) - end - - def get_ruby_bin_dir! - ruby_bin_dir = get_ruby_path!.join("..") - raise "#{ruby_bin_dir} is not a directory" unless File.directory?(ruby_bin_dir) - - return ruby_bin_dir - end - - it "handles empty binstubs" do - Tempfile.create("foo.txt") do |f| - expect { Pathname.new(f).open(&:readline) }.to raise_error(EOFError) - - binstub = LanguagePack::Helpers::BinstubWrapper.new(f.path) - expect(binstub.bad_shebang?).to be_falsey - expect(binstub.binary?).to be_falsey - end - end - - it "can determine if a file is binary or not" do - binstub = LanguagePack::Helpers::BinstubWrapper.new(get_ruby_path!) - - expect(binstub.bad_shebang?).to be_falsey - expect(binstub.binary?).to be_truthy - - Tempfile.create("foo.txt") do |f| - f.write("foo") - f.close - binstub = LanguagePack::Helpers::BinstubWrapper.new(f.path) - - expect(binstub.bad_shebang?).to be_falsey - expect(binstub.binary?).to be_falsey - end - end - - it "doesn't error on empty directories" do - Dir.mktmpdir do |dir| - warn_obj = Object.new - check = LanguagePack::Helpers::BinstubCheck.new(app_root_dir: dir, warn_object: warn_obj) - check.call - end - end - - it "does not raise an error when running against a directory with a binary file in it" do - ruby_bin_dir = get_ruby_bin_dir! - check = LanguagePack::Helpers::BinstubCheck.new(app_root_dir: ruby_bin_dir, warn_object: Object.new) - check.call - end - - it "checks binstubs and finds bad ones" do - Dir.mktmpdir do |dir| - bin_dir = Pathname.new(dir).join("bin") - bin_dir.mkpath - - # Bad binstub - bin_dir.join("bad_binstub_example").write(<<~EOM) - #!/usr/bin/env ruby2.5 - - nothing else matters - EOM - - # Good binstub - bin_dir.join("good_binstub_example").write(<<~EOM) - #!/usr/bin/env bash - - nothing else matters - EOM - bin_dir.join("good_binstub_example_two").write("#!/usr/bin/env ruby") - - warn_obj = Object.new - def warn_obj.warn(*args, **kwargs); @msg = args.first; end - def warn_obj.msg; @msg; end - - check = LanguagePack::Helpers::BinstubCheck.new(app_root_dir: dir, warn_object: warn_obj) - check.call - - expect(check.bad_binstubs.count).to eq(1) - expect(warn_obj.msg).to include("bin/bad_binstub_example") - end - end -end diff --git a/spec/helpers/bundler_wrapper_spec.rb b/spec/helpers/bundler_wrapper_spec.rb deleted file mode 100644 index b90c2c2f9..000000000 --- a/spec/helpers/bundler_wrapper_spec.rb +++ /dev/null @@ -1,138 +0,0 @@ -require 'spec_helper' - -describe "Bundle platform conversion" do - it "converts `bundle platform --ruby` for prerelease versions" do - actual = LanguagePack::Helpers::BundlerWrapper.platform_to_version("ruby 3.3.0.preview2") - expect(actual).to eq("ruby-3.3.0.preview2") - end - - it "converts `bundle platform --ruby` for released versions" do - actual = LanguagePack::Helpers::BundlerWrapper.platform_to_version("ruby 3.1.4") - expect(actual).to eq("ruby-3.1.4") - end -end - -describe "Bundler version detection" do - it "supports minor versions" do - wrapper_klass = LanguagePack::Helpers::BundlerWrapper - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 1.17.3") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("1")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["1"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.2.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.3")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.3"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.3.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.3")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.3"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.4.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.4")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.4"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.5.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.5")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.5"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.6.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.6")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.6"]) - - version = wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 2.999.7") - expect(wrapper_klass::BLESSED_BUNDLER_VERSIONS.key?("2.6")).to be_truthy - expect(version).to eq(wrapper_klass::BLESSED_BUNDLER_VERSIONS["2.6"]) - - expect { - wrapper_klass.detect_bundler_version(contents: "BUNDLED WITH\n 3.6.7") - }.to raise_error(wrapper_klass::UnsupportedBundlerVersion) - end -end - -describe "Multiple platform detection" do - it "reports true on bundler 2.2+" do - Dir.mktmpdir do |dir| - gemfile = Pathname(dir).join("Gemfile") - lockfile = Pathname(dir).join("Gemfile.lock").tap {|p| p.write("BUNDLED WITH\n 2.5.7") } - report = HerokuBuildReport.dev_null - - bundler = LanguagePack::Helpers::BundlerWrapper.new( - gemfile_path: gemfile, - report: report - ) - expect(bundler.supports_multiple_platforms?).to be_truthy - - expect(report.data).to eq( - { - "bundled_with" => "2.5.7", - "bundler_major" => "2", - "bundler_minor" => "5", - "bundler_patch" => "23", - "bundler_version_installed" => "2.5.23", - } - ) - end - end -end - -describe "BundlerWrapper mutates rubyopt" do - before(:each) do - if ENV['RUBYOPT'] - @original_rubyopt = ENV['RUBYOPT'] - ENV['RUBYOPT'] = ENV['RUBYOPT'].sub('-rbundler/setup', '') - end - - @bundler = LanguagePack::Helpers::BundlerWrapper.new - end - - after(:each) do - if ENV['RUBYOPT'] - ENV['RUBYOPT'] = @original_rubyopt - end - - @bundler.clean - end - - it "handles windows BUNDLED WITH" do - Dir.mktmpdir do |dir| - tmp_dir = Pathname(dir) - FileUtils.cp_r(fixture_path("windows_lockfile/."), tmp_dir) - - tmp_gemfile_path = tmp_dir.join("Gemfile") - tmp_gemfile_lock_path = tmp_dir.join("Gemfile.lock") - - expect(tmp_gemfile_lock_path.read).to match("BUNDLED") - - wrapper = LanguagePack::Helpers::BundlerWrapper.new(gemfile_path: tmp_gemfile_path ) - - expect(wrapper.version).to eq(LanguagePack::Helpers::BundlerWrapper::BLESSED_BUNDLER_VERSIONS["2"]) - - def wrapper.topic(*args); end # Silence output in tests - wrapper.bundler_version_escape_valve! - - expect(tmp_gemfile_lock_path.read).to_not match("BUNDLED") - end - end - - it "detects windows gemfiles" do - Hatchet::App.new("rails4_windows_mri193").in_directory_fork do |dir| - require "bundler" - Bundler.with_unbundled_env do - expect(@bundler.install.windows_gemfile_lock?).to be_truthy - end - end - end - - describe "when executing bundler" do - it "handles JRuby pre gemfiles" do - Hatchet::App.new("jruby-minimal").in_directory_fork do |dir| - require "bundler" - Bundler.with_unbundled_env do - @bundler.install - - expect(@bundler.ruby_version).to eq("ruby-2.3.1-p0-jruby-9.1.7.0") - end - end - end - end -end diff --git a/spec/helpers/config_spec.rb b/spec/helpers/config_spec.rb deleted file mode 100644 index 8f4f01a87..000000000 --- a/spec/helpers/config_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe "Boot Strap Config" do - it "matches toml config" do - require 'toml-rb' - config = TomlRB.load_file("buildpack.toml") - bootstrap_version = config["buildpack"]["ruby_version"] - expect(bootstrap_version).to eq(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER) - - expect(`ruby -v`).to match(Regexp.escape(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER)) - - ci_task = Pathname(".github").join("workflows").join("hatchet_app_cleaner.yml").read - ci_task_yml = YAML.load(ci_task) - task = ci_task_yml["jobs"]["hatchet-app-cleaner"]["steps"].detect {|step| step["uses"].match?(/ruby\/setup-ruby/)} or raise "Not found" - expect(task["with"]["ruby-version"]).to match(LanguagePack::RubyVersion::BOOTSTRAP_VERSION_NUMBER) - end -end diff --git a/spec/helpers/download_presence_spec.rb b/spec/helpers/download_presence_spec.rb deleted file mode 100644 index 7d0da9651..000000000 --- a/spec/helpers/download_presence_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -require "spec_helper" - -describe LanguagePack::Helpers::DownloadPresence do - it "handles multi-arch transitions for files that exist" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: ["heroku-24"], - file_name: 'ruby-3.1.4.tgz', - stacks: ["heroku-22", "heroku-24"], - arch: "amd64" - ) - - download.call - - expect(download.next_stack(current_stack: "heroku-22")).to eq("heroku-24") - expect(download.next_stack(current_stack: "heroku-24")).to be_falsey - - expect(download.exists_on_next_stack?(current_stack:"heroku-22")).to be_truthy - end - - it "handles multi-arch transitions for files that do not exist" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: ["heroku-24"], - file_name: 'ruby-3.0.5.tgz', - stacks: ["heroku-20", "heroku-24"], - arch: "amd64" - ) - - download.call - - expect(download.next_stack(current_stack: "heroku-20")).to eq("heroku-24") - expect(download.next_stack(current_stack: "heroku-24")).to be_falsey - - expect(download.exists_on_next_stack?(current_stack:"heroku-20")).to be_falsey - end - - it "knows if exists on the next stack" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: 'ruby-3.1.4.tgz', - stacks: ['heroku-20', 'heroku-22'], - arch: nil - ) - - download.call - - expect(download.next_stack(current_stack: "heroku-20")).to eq("heroku-22") - expect(download.next_stack(current_stack: "heroku-22")).to be_falsey - - expect(download.exists_on_next_stack?(current_stack:"heroku-20")).to be_truthy - end - - it "detects when a package is present on higher stacks" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: 'ruby-2.6.5.tgz', - stacks: ['cedar-14', 'heroku-16', 'heroku-18'], - arch: nil - ) - - download.call - - expect(download.exists?).to eq(true) - expect(download.valid_stack_list).to eq(['cedar-14', 'heroku-16', 'heroku-18']) - - expect(download.exists_on_next_stack?(current_stack: "heroku-16")).to be_truthy - expect(download.next_stack(current_stack: "heroku-16")).to eq("heroku-18") - end - - it "detects when a package is not present on higher stacks" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: 'ruby-1.9.3.tgz', - stacks: ['cedar-14', 'heroku-16', 'heroku-18'], - arch: nil - ) - - download.call - - expect(download.exists?).to eq(true) - expect(download.valid_stack_list).to eq(['cedar-14']) - end - - it "detects when a package is present on two stacks but not a third" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: 'ruby-2.3.0.tgz', - stacks: ['cedar-14', 'heroku-16', 'heroku-18'], - arch: nil - ) - - download.call - - expect(download.exists?).to eq(true) - expect(download.valid_stack_list).to eq(['cedar-14', 'heroku-16']) - end - - it "detects when a package does not exist" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: 'does-not-exist.tgz', - stacks: ['cedar-14', 'heroku-16', 'heroku-18'], - arch: nil - ) - - download.call - - expect(download.exists?).to eq(false) - expect(download.valid_stack_list).to eq([]) - end - - it "detects default ruby version" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: "ruby-3.1.1.tgz", - arch: nil - ) - - download.call - - expect(download.exists?).to eq(true) - expect(download.valid_stack_list).to include(LanguagePack::Helpers::DownloadPresence::STACKS.last) - end - - it "handles the current stack not being in the known stacks list" do - download = LanguagePack::Helpers::DownloadPresence.new( - multi_arch_stacks: [], - file_name: "#{LanguagePack::RubyVersion::DEFAULT_VERSION}.tgz", - arch: nil - ) - - download.call - - expect(download.supported_stack?(current_stack: "unknown-stack")).to be_falsey - expect(download.next_stack(current_stack: "unknown-stack")).to be_nil - expect(download.exists_on_next_stack?(current_stack:"unknown-stack")).to be_falsey - end -end diff --git a/spec/helpers/fetcher_spec.rb b/spec/helpers/fetcher_spec.rb deleted file mode 100644 index c1a432e86..000000000 --- a/spec/helpers/fetcher_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe "Fetches" do - LanguagePack::Helpers::BundlerWrapper::BLESSED_BUNDLER_VERSIONS.each do |_, version| - it "bundler #{version}" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - lockfile = Pathname("Gemfile.lock") - FileUtils.touch(lockfile) - lockfile.write("BUNDLED WITH\n #{version}") - - fetcher = LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL) - fetcher.fetch_untar("bundler/#{LanguagePack::Helpers::BundlerWrapper.new.dir_name}.tgz") - - expect(run!("ls bin")).to match("bundle") - end - end - end - end -end diff --git a/spec/helpers/heroku_build_report_spec.rb b/spec/helpers/heroku_build_report_spec.rb deleted file mode 100644 index 069da1cbc..000000000 --- a/spec/helpers/heroku_build_report_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' - -describe "Build report" do - it "handles complex object serialization by converting them to strings" do - Dir.mktmpdir do |dir| - path = Pathname(dir).join(".report.yml") - report = HerokuBuildReport::YamlReport.new( - path: path - ) - value = Gem::Version.new("3.4.2") - expect(report.complex_object?(value)).to eq(true) - expect(value.to_yaml).to_not eq(value.to_s.to_yaml) - report.capture("key" => value) - - expect(report.data).to eq({"key" => "3.4.2"}) - expect(path.read).to eq(<<~EOF) - --- - key: 3.4.2 - EOF - end - end - - it "writes valid yaml" do - Dir.mktmpdir do |dir| - path = Pathname(dir).join(".report.yml") - report = HerokuBuildReport::YamlReport.new( - path: path - ) - report.capture( - "string" => "'with single quotes'", - "string_plain" => "plain", - "number" => 22, - "boolean" => true, - ) - - expect(path.read).to eq(<<~EOF) - --- - string: "'with single quotes'" - string_plain: plain - number: 22 - boolean: true - EOF - end - end -end diff --git a/spec/helpers/node_installer_spec.rb b/spec/helpers/node_installer_spec.rb deleted file mode 100644 index 3ba393bb2..000000000 --- a/spec/helpers/node_installer_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe LanguagePack::Helpers::NodeInstaller do - describe "#install" do - LanguagePack::Base::KNOWN_ARCHITECTURES.each do |arch| - it "should extract a node binary on #{arch}" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - installer = LanguagePack::Helpers::NodeInstaller.new(arch: "arm64") - installer.install - - expect(File.exist?("node")).to be(true) - end - end - end - end - end -end diff --git a/spec/helpers/outdated_ruby_version_spec.rb b/spec/helpers/outdated_ruby_version_spec.rb deleted file mode 100644 index 6c24ecf49..000000000 --- a/spec/helpers/outdated_ruby_version_spec.rb +++ /dev/null @@ -1,135 +0,0 @@ -require "spec_helper" - -describe LanguagePack::Helpers::OutdatedRubyVersion do - let(:stack) { "heroku-16" } - let(:fetcher) { - LanguagePack::Fetcher.new(LanguagePack::Base::VENDOR_URL, stack: stack) - } - - it "handles amd â†—ï¸ architecture on heroku-24" do - ruby_version = LanguagePack::RubyVersion.new("ruby-3.1.0") - fetcher = LanguagePack::Fetcher.new( - LanguagePack::Base::VENDOR_URL, - stack: "heroku-24", - arch: "amd64" - ) - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - - outdated.call - expect(outdated.suggested_ruby_minor_version).to eq("3.1.7") - end - - it "handles arm 💪 architecture on heroku-24" do - ruby_version = LanguagePack::RubyVersion.new("ruby-3.3.0") - fetcher = LanguagePack::Fetcher.new( - LanguagePack::Base::VENDOR_URL, - stack: "heroku-24", - arch: "arm64" - ) - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - - outdated.call - expect(outdated.suggested_ruby_minor_version).to eq("3.3.7") - end - - it "finds the latest version on a stack" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.5") - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - - outdated.call - expect(outdated.suggested_ruby_minor_version).to eq("2.2.10") - expect(outdated.eol?).to eq(true) - expect(outdated.maybe_eol?).to eq(true) - end - - it "detects returns original ruby version when using the latest" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.10") - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - - outdated.call - expect(outdated.suggested_ruby_minor_version).to eq("2.2.10") - expect(outdated.latest_minor_version?).to be_truthy - end - - it "recommends a non EOL version of Ruby" do - ruby_version_one = LanguagePack::RubyVersion.new("ruby-2.1.10") - ruby_version_two = LanguagePack::RubyVersion.new("ruby-2.2.10") - - outdated_one = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version_one, - fetcher: fetcher - ) - outdated_two = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version_two, - fetcher: fetcher - ) - - outdated_one.call - outdated_two.call - - expect(outdated_one.eol?).to be_truthy - expect(outdated_one.maybe_eol?).to be_truthy - - expect(outdated_two.eol?).to be_truthy - expect(outdated_one.maybe_eol?).to be_truthy - - suggested_one = outdated_one.suggest_ruby_eol_version - expect(suggested_one).to eq(outdated_two.suggest_ruby_eol_version) - expect(suggested_one.chars.last).to eq("x") # i.e. 2.5.x - - actual = Gem::Version.new(suggested_one) - expect(actual).to be > Gem::Version.new("2.4.x") - end - - it "does not recommend EOL for recent ruby version" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.2.10") - - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - - outdated.call - - good_version = outdated.suggest_ruby_eol_version.sub("x", "0") - ruby_version = LanguagePack::RubyVersion.new("ruby-#{good_version}") - - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: fetcher - ) - outdated.call - - expect(outdated.eol?).to be_falsey - expect(outdated.maybe_eol?).to be_falsey - end - - it "can call eol? on the latest Ruby version" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.0") - - new_fetcher = fetcher.dup - def new_fetcher.exists?(value); false; end - - outdated = LanguagePack::Helpers::OutdatedRubyVersion.new( - current_ruby_version: ruby_version, - fetcher: new_fetcher - ) - - outdated.call - - expect(outdated.eol?).to be_falsey - expect(outdated.maybe_eol?).to be_falsey - end -end diff --git a/spec/helpers/rails_runner_spec.rb b/spec/helpers/rails_runner_spec.rb deleted file mode 100644 index 1488c8677..000000000 --- a/spec/helpers/rails_runner_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'spec_helper' - -describe "Rails Runner" do - around(:each) do |test| - original_path = ENV["PATH"] - ENV["PATH"] = "./bin/:#{ENV['PATH']}" - - Dir.mktmpdir do |tmpdir| - Dir.chdir(tmpdir) do - test.run - end - end - ensure - ENV["PATH"] = original_path if original_path - end - - it "config objects build propperly formatted commands" do - rails_runner = LanguagePack::Helpers::RailsRunner.new - local_storage = rails_runner.detect("active_storage.service") - - expected = 'rails runner "begin; puts %Q{heroku.detecting.config.for.active_storage.service=#{Rails.application.config.try(:active_storage).try(:service)}}; rescue => e; end;"' - expect(rails_runner.command).to eq(expected) - - rails_runner.detect("assets.compile") - - expected = 'rails runner "begin; puts %Q{heroku.detecting.config.for.active_storage.service=#{Rails.application.config.try(:active_storage).try(:service)}}; rescue => e; end; begin; puts %Q{heroku.detecting.config.for.assets.compile=#{Rails.application.config.try(:assets).try(:compile)}}; rescue => e; end;"' - expect(rails_runner.command).to eq(expected) - end - - it "calls run through child object" do - rails_runner = LanguagePack::Helpers::RailsRunner.new - def rails_runner.call; @called ||= 0 ; @called += 1; "" end - def rails_runner.called; @called; end - - local_storage = rails_runner.detect("active_storage.service") - local_storage.success? - expect(rails_runner.called).to eq(1) - - local_storage.success? - local_storage.did_match?("foo") - expect(rails_runner.called).to eq(1) - end - - it "calls a mock interface" do - mock_rails_runner - expect(File.executable?("bin/rails")).to eq(true) - - rails_runner = LanguagePack::Helpers::RailsRunner.new - local_storage = rails_runner.detect("active_storage.service") - local_storage = rails_runner.detect("foo.bar") - - expect(rails_runner.output).to match("heroku.detecting.config.for.active_storage.service=active_storage.service") - expect(rails_runner.output).to match("heroku.detecting.config.for.foo.bar=foo.bar") - expect(rails_runner.success?).to be(true) - end - - it "timeout works as expected" do - mock_rails_runner("pid = Process.spawn('sleep 5'); Process.wait(pid)") - - diff = time_it do - rails_runner = LanguagePack::Helpers::RailsRunner.new(false, 0.01) - local_storage = rails_runner.detect("active_storage.service") - expect(rails_runner.success?).to eq(false) - expect(rails_runner.timeout?).to eq(true) - end - - expect(diff < 1).to eq(true), "expected time difference #{diff} to be less than 1 second, but was longer" - end - - it "failure in one task does not cause another to fail" do - mock_rails_runner('raise "bad" if value == :bad') - - rails_runner = LanguagePack::Helpers::RailsRunner.new(false, 1) - bad_value = rails_runner.detect("bad.value") - local_storage = rails_runner.detect("active_storage.service") - - expect(!!bad_value.success?).to eq(false) - expect(!!local_storage.success?).to eq(true) - end - - it "does not fail when there is an invalid byte sequence" do - mock_rails_runner('puts "hi \255"') - - rails_runner = LanguagePack::Helpers::RailsRunner.new - local_storage = rails_runner.detect("active_storage.service") - - expect(local_storage.success?).to be_truthy - end - - def time_it - start = Time.now - yield - return Time.now - start - end - - def mock_rails_runner(try_code = "") - executable_contents = <<-FILE -#!/usr/bin/env ruby -require 'ostruct' - -module Rails; end -def Rails.application - OpenStruct.new(config: TryMock.new) # Rails.application.config #=> TryMock instance -end - -# Mock object used to record calls -# for example: -# -# obj = Try.new -# obj.try(:active_storage).try(:service) -# puts obj.to_s # => "active_storage.service" -# -class TryMock - def initialize(array = []) - @try_array = array - end - - def try(value) - @try_array << value - #{try_code} - return TryMock.new(@try_array) - end - - def to_s - @try_array.join(".") - end -end - -ARGV.shift # remove "runner" -eval(ARGV.join(" ")) # Execute command passed in -FILE - FileUtils.mkdir("bin") - File.open("bin/rails", "w") { |f| f << executable_contents } - File.chmod(0777, "bin/rails") - - # BUILDPACK_LOG_FILE support for logging - FileUtils.mkdir("tmp") - FileUtils.touch("buildpack.log") - end -end diff --git a/spec/helpers/rake_runner_spec.rb b/spec/helpers/rake_runner_spec.rb deleted file mode 100644 index eb05bbcab..000000000 --- a/spec/helpers/rake_runner_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' - -describe "Rake Runner" do - it "runs rake tasks that exist" do - Hatchet::App.new('asset_precompile_pass').in_directory_fork do - rake = LanguagePack::Helpers::RakeRunner.new.load_rake_tasks! - task = rake.task("assets:precompile") - task.invoke - - expect(task.output).to match("success!") - expect(task.status).to eq(:pass) - expect(task.time).not_to be_nil - end - end - - it "detects when rake tasks fail" do - Hatchet::App.new('asset_precompile_fail').in_directory_fork do - rake = LanguagePack::Helpers::RakeRunner.new.load_rake_tasks! - task = rake.task("assets:precompile") - task.invoke - - expect(task.output).to match("assets:precompile fails") - expect(task.status).to eq(:fail) - expect(task.time).not_to be_nil - end - end - - it "can show errors from bad Rakefiles" do - Hatchet::App.new('bad_rakefile').in_directory_fork do - rake = LanguagePack::Helpers::RakeRunner.new.load_rake_tasks! - task = rake.task("assets:precompile") - expect(rake.rakefile_can_load?).to be_falsey - expect(task.task_defined?).to be_falsey - end - end - - it "detects if task is missing" do - Hatchet::App.new('asset_precompile_not_found').in_directory_fork do - task = LanguagePack::Helpers::RakeRunner.new.task("assets:precompile") - expect(task.task_defined?).to be_falsey - end - end - - it "detects when no rakefile is present" do - Hatchet::App.new('no_rakefile').in_directory_fork do - runner = LanguagePack::Helpers::RakeRunner.new - expect(runner.rakefile_can_load?).to be_falsey - end - end -end diff --git a/spec/helpers/ruby_version_spec.rb b/spec/helpers/ruby_version_spec.rb deleted file mode 100644 index 70b1f27bd..000000000 --- a/spec/helpers/ruby_version_spec.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'spec_helper' - -describe "RubyVersion" do - before(:each) do - if ENV['RUBYOPT'] - @original_rubyopt = ENV['RUBYOPT'] - ENV['RUBYOPT'] = ENV['RUBYOPT'].sub('-rbundler/setup', '') - end - @bundler = LanguagePack::Helpers::BundlerWrapper.new - end - - after(:each) do - if ENV['RUBYOPT'] - ENV['RUBYOPT'] = @original_rubyopt - end - @bundler.clean - end - - it "knows the next logical version" do - version_number = "2.5.0" - ruby_version = LanguagePack::RubyVersion.new("ruby-#{version_number}-p0", is_new: true) - version = "ruby-#{version_number}" - - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") - expect(ruby_version.next_logical_version).to eq("ruby-2.5.1") - expect(ruby_version.next_logical_version(2)).to eq("ruby-2.5.2") - expect(ruby_version.next_logical_version(20)).to eq("ruby-2.5.20") - - # Minor version - expect(ruby_version.next_minor_version).to eq("ruby-2.6.0") - expect(ruby_version.next_minor_version(2)).to eq("ruby-2.7.0") - - # Major Version - expect(ruby_version.next_major_version).to eq("ruby-3.0.0") - expect(ruby_version.next_major_version(2)).to eq("ruby-4.0.0") - end - - it "does not include patchlevels when the patchlevel is negative for download" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.0.0-p-1") - expect(ruby_version.version_for_download).to eq("ruby-2.0.0") - - ruby_version = LanguagePack::RubyVersion.new("ruby-2.4.0-p-1") - expect(ruby_version.version_for_download).to eq("ruby-2.4.0") - end - - it "detects Ruby 2.6.0, 2.6.1 and 2.6.2 as needing a warning" do - ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.0") - expect(ruby_version.warn_ruby_26_bundler?).to be true - ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.1") - expect(ruby_version.warn_ruby_26_bundler?).to be true - ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.2") - expect(ruby_version.warn_ruby_26_bundler?).to be true - - ruby_version = LanguagePack::RubyVersion.new("ruby-2.6.3") - expect(ruby_version.warn_ruby_26_bundler?).to be false - ruby_version = LanguagePack::RubyVersion.new("ruby-2.5.3") - expect(ruby_version.warn_ruby_26_bundler?).to be false - ruby_version = LanguagePack::RubyVersion.new("ruby-2.7.1") - expect(ruby_version.warn_ruby_26_bundler?).to be false - end - - it "correctly sets default ruby versions" do - Hatchet::App.new("default_ruby").in_directory_fork do |dir| - require 'bundler' - Bundler.with_unbundled_env do - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = LanguagePack::RubyVersion::DEFAULT_VERSION_NUMBER - version = LanguagePack::RubyVersion::DEFAULT_VERSION - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.to_gemfile).to eq("ruby '#{version_number}'") - expect(ruby_version.engine).to eq(:ruby) - expect(ruby_version.default?).to eq(true) - end - end - end - - it "detects Ruby from Gemfile.lock" do - Hatchet::App.new("default_ruby").in_directory_fork do |_| - require 'bundler' - dir = Pathname(Dir.pwd) - Bundler.with_unbundled_env do - dir.join("Gemfile").write(<<~EOF) - source "https://rubygems.org" - - gem 'rake' - ruby '3.2.3' - EOF - dir.join("Gemfile.lock").write(<<~EOF) - GEM - remote: https://rubygems.org/ - specs: - rake (13.2.1) - - PLATFORMS - arm64-darwin-22 - ruby - x86_64-linux - - DEPENDENCIES - rake - - RUBY VERSION - ruby 3.2.3p157 - - BUNDLED WITH - 2.4.19 - EOF - - ruby_version = LanguagePack::RubyVersion.new(@bundler.install.ruby_version, is_new: true) - version_number = "3.2.3" - version = "ruby-#{version_number}" - expect(ruby_version.version_without_patchlevel).to eq(version) - expect(ruby_version.engine_version).to eq(version_number) - expect(ruby_version.engine).to eq(:ruby) - end - end - end - - it "detects non mri engines" do - Hatchet::App.new("default_ruby").in_directory_fork do |_| - require 'bundler' - dir = Pathname(Dir.pwd) - Bundler.with_unbundled_env do - dir.join("Gemfile").write(<<~EOF) - source "https://rubygems.org" - - ruby '2.6.8', engine: 'jruby', engine_version: '9.3.6.0' - EOF - dir.join("Gemfile.lock").write(<<~EOF) - GEM - remote: https://rubygems.org/ - specs: - PLATFORMS - java - - DEPENDENCIES - - RUBY VERSION - ruby 2.6.8p001 (jruby 9.3.6.0) - - BUNDLED WITH - 2.3.25 - EOF - - ruby_version = LanguagePack::RubyVersion.new( - @bundler.install.ruby_version, - is_new: true - ) - version_number = "2.6.8" - engine_version = "9.3.6.0" - engine = :jruby - expect(ruby_version.version_without_patchlevel).to eq("ruby-#{version_number}-#{engine}-#{engine_version}") - expect(ruby_version.engine_version).to eq(engine_version) - expect(ruby_version.engine).to eq(engine) - end - end - end -end diff --git a/spec/helpers/shell_spec.rb b/spec/helpers/shell_spec.rb deleted file mode 100644 index 0d4eab188..000000000 --- a/spec/helpers/shell_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe "ShellHelpers" do - module RecordPuts - attr_reader :puts_calls, :print_calls - def puts(*args) - @puts_calls ||= [] - @puts_calls << args - end - - def print(*args) - @print_calls ||= [] - @print_calls << args - end - end - - class FakeShell - include RecordPuts - include LanguagePack::ShellHelpers - end - - describe "pipe" do - it "does not double append newlines" do - sh = FakeShell.new - sh.pipe('bundle install') - first_line = sh.print_calls.first.first - expect(first_line.end_with?("\n\n")).to be(false) - end - end - - describe "mcount" do - it "logs to a file" do - begin - original = ENV["BUILDPACK_LOG_FILE"] - Tempfile.open("logfile.log") do |f| - ENV["BUILDPACK_LOG_FILE"] = f.path - FakeShell.new.mcount "foo" - expect(File.read(f.path)).to match("count#buildpack.ruby.foo=1") - end - ensure - ENV["BUILDPACK_LOG_FILE"] = original - end - end - end - - describe "#command_options_to_string" do - it "formats ugly keys correctly" do - env = {%Q{ un"matched } => "bad key"} - result = FakeShell.new.command_options_to_string("bundle install", env: env) - expected = %r{env \\ un\\\"matched\\ =bad\\ key bash -c bundle\\ install 2>&1} - expect(result.strip).to match(expected) - end - - it "formats ugly values correctly" do - env = {"BAD VALUE" => %Q{ )(*&^%$#'$'\n''@!~\'\ }} - result = FakeShell.new.command_options_to_string("bundle install", env: env) - expected = %r{env BAD\\ VALUE=\\ \\\)\\\(\\\*\\&\\\^\\%\\\$\\#\\'\\\$\\''\n'\\'\\'@\\!\\~\\'\\ bash -c bundle\\ install 2>&1} - expect(result.strip).to match(expected) - end - end - - describe "#run!" do - it "retries failed commands when passed max_attempts: > 1" do - sh = FakeShell.new - expect { sh.run!("false", max_attempts: 3) }.to raise_error(StandardError) - - expect(sh.print_calls).to eq([ - [" Command: 'false' failed on attempt 1 of 3.\n"], - [" Command: 'false' failed on attempt 2 of 3.\n"], - ]) - end - end - - describe "#puts" do - context 'when the message has an invalid utf-8 character' do - it 'no error is raised' do - sh = FakeShell.new - - bad_lines = File.read("spec/fixtures/invalid_encoding.log") - sh.puts(bad_lines) - end - - it 'catches it just in case' do - sh = FakeShell.new - - def sh.print(string); string.strip; end - def sh.mcount(*args); @error_caught = true; end - - bad_lines = File.read("spec/fixtures/invalid_encoding.log") - expect { sh.puts(bad_lines) }.to raise_error(ArgumentError) - - error_caught = sh.instance_variable_get(:"@error_caught") - expect(error_caught).to eq(true) - end - end - end -end diff --git a/spec/helpers/stale_file_cleaner_spec.rb b/spec/helpers/stale_file_cleaner_spec.rb deleted file mode 100644 index 5c23f1a35..000000000 --- a/spec/helpers/stale_file_cleaner_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -describe "Cleans Stale Files" do - - it "removes files if they go over the limit" do - file_size = 1000 - Dir.mktmpdir do |dir| - old_file = create_file_with_size_in(file_size, dir) - sleep 1 # need mtime of files to be different - new_file = create_file_with_size_in(file_size, dir) - - expect(old_file.exist?).to be_truthy - expect(new_file.exist?).to be_truthy - - ::LanguagePack::Helpers::StaleFileCleaner.new(dir).clean_over(2*file_size - 50) - - expect(old_file.exist?).to be_falsey - expect(new_file.exist?).to be_truthy - end - end - - it "leaves files if they are under the limit" do - file_size = 1000 - Dir.mktmpdir do |dir| - old_file = create_file_with_size_in(file_size, dir) - sleep 1 # need mtime of files to be different - new_file = create_file_with_size_in(file_size, dir) - - expect(old_file.exist?).to be_truthy - expect(new_file.exist?).to be_truthy - dir_size = File.stat(dir) - - ::LanguagePack::Helpers::StaleFileCleaner.new(dir).clean_over(2*file_size + 50) - - expect(old_file.exist?).to be_truthy - expect(new_file.exist?).to be_truthy - end - end -end diff --git a/spec/helpers/yarn_installer_spec.rb b/spec/helpers/yarn_installer_spec.rb deleted file mode 100644 index 8359428e6..000000000 --- a/spec/helpers/yarn_installer_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe LanguagePack::Helpers::YarnInstaller do - describe "#install" do - - it "should extract the yarn package" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - installer = LanguagePack::Helpers::YarnInstaller.new - installer.install - - # webpacker gem checks for yarnpkg - # https://github.com/rails/webpacker/blob/master/lib/install/bin/yarn.tt#L5 - expect(File.exist?("yarn-v#{installer.version}/bin/yarnpkg")).to be(true) - end - end - end - end -end diff --git a/spec/installers/heroku_ruby_installer_spec.rb b/spec/installers/heroku_ruby_installer_spec.rb deleted file mode 100644 index fa6c10d11..000000000 --- a/spec/installers/heroku_ruby_installer_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -require "spec_helper" - -describe LanguagePack::Installers::HerokuRubyInstaller do - def installer(report: HerokuBuildReport::GLOBAL) - LanguagePack::Installers::HerokuRubyInstaller.new( - multi_arch_stacks: [], - stack: "cedar-14", - arch: nil, - report: report - ) - end - - def ruby_version - LanguagePack::RubyVersion.new("ruby-2.3.3") - end - - describe "#fetch_unpack" do - it "should fetch and unpack mri" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - installer.fetch_unpack(ruby_version, dir) - - expect(File).to exist("bin/ruby") - end - end - end - end - - describe "#install" do - it "should install ruby and setup binstubs" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - report = HerokuBuildReport.dev_null - installer(report: report).install(ruby_version, "#{dir}/vendor/ruby") - - expect(File.symlink?("#{dir}/bin/ruby")).to be true - expect(File.symlink?("#{dir}/bin/ruby.exe")).to be true - expect(File).to exist("#{dir}/vendor/ruby/bin/ruby") - - expect(report.data["ruby.version"]).to eq("2.3.3") - expect(report.data["ruby.engine"]).to eq(:ruby) - expect(report.data["ruby.engine.version"]).to eq(report.data["ruby.version"]) - expect(report.data["ruby.major"]).to eq(2) - expect(report.data["ruby.minor"]).to eq(3) - expect(report.data["ruby.patch"]).to eq(3) - end - end - end - - it "should report jruby correctly" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - report = HerokuBuildReport.dev_null - - LanguagePack::Installers::HerokuRubyInstaller.new( - multi_arch_stacks: ["heroku-24"], - stack: "heroku-24", - arch: "arm64", - report: report - ).install( - LanguagePack::RubyVersion.new("ruby-3.1.4-p0-jruby-9.4.9.0"), - "#{dir}/vendor/ruby" - ) - - expect(report.data["ruby.version"]).to eq("3.1.4") - expect(report.data["ruby.engine"]).to eq(:jruby) - expect(report.data["ruby.engine.version"]).to eq("9.4.9.0") - expect(report.data["ruby.major"]).to eq(3) - expect(report.data["ruby.minor"]).to eq(1) - expect(report.data["ruby.patch"]).to eq(4) - end - end - end - end -end diff --git a/spec/rake/deploy_check_spec.rb b/spec/rake/deploy_check_spec.rb deleted file mode 100644 index c2c85b0a9..000000000 --- a/spec/rake/deploy_check_spec.rb +++ /dev/null @@ -1,150 +0,0 @@ -require "spec_helper" -require "rake/deploy_check" - -describe "A helper class for deploying" do - describe "tests that hit github" do - it "know remote tags" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - run!("touch foo; git init; git add .; git commit -m first") - - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - expect(deploy.remote_tag_array.class).to eq(Array) - expect(deploy.remote_tag_array).to include("v218") - - expect(deploy.remote_tag_matches?(local_sha: "nope")).to be_falsey - expect(deploy.remote_tag_matches?(remote_sha: "nope")).to be_falsey - end - end - end - - it "remote sha" do - deploy = DeployCheck.new(github: "sharpstone/do_not_delete_or_modify") - expect(deploy.remote_commit_sha).to eq("3a9ff6433a05560acfd06dda03a11605a96ae133") - end - - it "local_commit_sha" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - 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") - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - expect(deploy.local_commit_sha).to eq("6e642963acec0ff64af51bd6fba8db3c4176ed6e") - end - end - end - end - - describe "tests that do NOT hit github" do - it "checks multiple things" do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - deploy.instance_variable_set("@methods_called", []) - - def deploy.check_version!; @methods_called << :check_version! ;end - def deploy.check_unstaged!; @methods_called << :check_unstaged! ;end - def deploy.check_branch!; @methods_called << :check_branch! ;end - def deploy.check_changelog!; @methods_called << :check_changelog! ;end - def deploy.check_sync!; @methods_called << :check_sync! ;end - - deploy.check! - - methods_called = deploy.instance_variable_get("@methods_called") - expect(methods_called).to include(:check_version!) - expect(methods_called).to include(:check_unstaged!) - expect(methods_called).to include(:check_branch!) - expect(methods_called).to include(:check_changelog!) - expect(methods_called).to include(:check_sync!) - end - - it "checks version" do - ["v123abc", "123", "V123"].each do |bad_version| - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby", next_version: bad_version) - expect { - deploy.check_version! - }.to raise_error(/Must look like a version/) - end - end - - it "checks local sha and remote sha match" do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - expect { - deploy.check_sync!(local_sha: "a", remote_sha: "not a") - }.to raise_error(/Must be in-sync/) - - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - deploy.check_sync!( - local_sha: "cbe100933b1e50953f0da35aafc50374ae2a31f9", - remote_sha: "cbe100933b1e50953f0da35aafc50374ae2a31f9" - ) - end - - it "github url" do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - expect(deploy.github_url).to eq("https://github.com/heroku/heroku-buildpack-ruby") - end - - it "knows the next version" do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby", next_version: "v123") - - def deploy.remote_tag_array; [ "v123" ] ; end - - expect(deploy.tag_exists_on_remote?).to be_truthy - - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby", next_version: "v124") - - def deploy.remote_tag_array; [ "v123" ] ; end - - expect(deploy.tag_exists_on_remote?).to be_falsey - end - - it "checks remote tags for existance" do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - - def deploy.remote_tag_array; [ "v123" ] ; end - - expect(deploy.next_version).to eq("v124") - end - - it "checks unstaged" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - run!("echo 'blerg' >> foo.txt") - run!("git init .; git add . ; git commit -m first") - deploy.check_unstaged! - - run!("echo 'foo' >> foo.txt") - expect { deploy.check_unstaged! }.to raise_error(/Must have all changes committed/) - end - end - end - - it "checks branch" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby") - run!("echo 'blerg' >> foo.txt") - run!("git init .; git add . ; git commit -m first") - run!("git checkout -B main") - deploy.check_branch! - - expect { deploy.check_branch!("not_main") }.to raise_error(/Must be on main branch/) - end - end - end - - it "checks CHANGELOG" do - Dir.mktmpdir do |dir| - Dir.chdir(dir) do - deploy = DeployCheck.new(github: "heroku/heroku-buildpack-ruby", next_version: "v999") - run!("touch CHANGELOG.md") - expect { - deploy.check_changelog! - }.to raise_error(/Expected CHANGELOG.md to include \[v999\]/) - - run!("echo '## [v999]' >> CHANGELOG.md") - deploy.check_changelog! - end - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 82b0b8e92..000000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'rspec/core' -require 'hatchet' -require 'fileutils' -require 'stringio' -require 'hatchet' -require 'rspec/retry' -require 'language_pack' -require 'language_pack/shell_helpers' - -ENV["HATCHET_BUILDPACK_BASE"] ||= "https://github.com/heroku/heroku-buildpack-ruby" - -ENV['RACK_ENV'] = 'test' - -DEFAULT_STACK = 'heroku-24' - - -def hatchet_path(path = "") - Pathname(__FILE__).join("../../repos").expand_path.join(path) -end - -RSpec.configure do |config| - config.filter_run focused: true unless ENV['IS_RUNNING_ON_CI'] - config.run_all_when_everything_filtered = true - config.alias_example_to :fit, focused: true - config.full_backtrace = true - config.verbose_retry = true # show retry status in spec process - config.default_retry_count = 2 if ENV['IS_RUNNING_ON_CI'] # retry all tests that fail again - config.example_status_persistence_file_path = 'spec/examples.txt' - - config.expect_with :rspec do |c| - c.max_formatted_output_length = Float::INFINITY - c.syntax = :expect - end - config.mock_with :nothing - config.include LanguagePack::ShellHelpers -end - -def successful_body(app, options = {}) - retry_limit = options[:retry_limit] || 50 - url = "http://#{app.name}.herokuapp.com" - Excon.get(url, :idempotent => true, :expects => 200, :retry_limit => retry_limit).body -end - -def create_file_with_size_in(size, dir) - name = File.join(dir, SecureRandom.hex(16)) - File.open(name, 'w') {|f| f.print([ 1 ].pack("C") * size) } - Pathname.new name -end - -if ENV['TRAVIS'] - # Don't execute tests against "merge" commits - exit 0 if ENV['TRAVIS_PULL_REQUEST'] != 'false' && ENV['TRAVIS_BRANCH'] == 'master' -end - -def buildpack_path - File.expand_path(File.join("../.."), __FILE__) -end - -def fixture_path(path) - Pathname.new(__FILE__).join("../fixtures").expand_path.join(path) -end - -def set_lts_ruby_version - Pathname("Gemfile").write("ruby '3.3.6'", mode: "a") -end - -def set_bundler_version(version: ) - gemfile_lock = Pathname("Gemfile.lock").read - - if version == :default - version = "" - else - version = "BUNDLED WITH\n #{version}" - end - gemfile_lock.gsub!(/^BUNDLED WITH$(\r?\n) (?\d+)\.(?\d+)\.\d+/m, version) - gemfile_lock << "\n#{version}" unless gemfile_lock.match?(/^BUNDLED WITH/) - - Pathname("Gemfile.lock").write(gemfile_lock) -end - -def rails_lts_config - { 'BUNDLE_GEMS__RAILSLTS__COM' => ENV["RAILS_LTS_CREDS"] } -end - -def rails_lts_stack - "heroku-22" -end - -def hatchet_path(path = "") - Pathname.new(__FILE__).join("../../repos").expand_path.join(path) -end - -def dyno_status(app, ps_name = "web") - app - .api_rate_limit.call - .dyno - .list(app.name) - .detect {|x| x["type"] == ps_name } -end - -def wait_for_dyno_boot(app, ps_name = "web", sleep_val = 1) - while ["starting", "restarting"].include?(dyno_status(app, ps_name)["state"]) - sleep sleep_val - end - dyno_status(app, ps_name) -end - -def web_boot_status(app) - wait_for_dyno_boot(app)["state"] -end - -def root_dir - Pathname(__dir__).join("..") -end diff --git a/spec/unit/bash_functions_spec.rb b/spec/unit/bash_functions_spec.rb deleted file mode 100644 index 5226e656d..000000000 --- a/spec/unit/bash_functions_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'spec_helper' - -describe "Bash functions" do - it "Detects jruby in the Gemfile.lock" do - Dir.mktmpdir do |dir| - dir = Pathname(dir) - dir.join("Gemfile.lock").write <<~EOM - RUBY VERSION - ruby 2.5.7p001 (jruby 9.2.13.0) - EOM - - - out = exec_with_bash_functions <<~EOM - which_java() - { - return 1 - } - - if detect_needs_java "#{dir}"; then - echo "jruby detected" - else - echo "nope" - fi - EOM - - expect(out).to eq("jruby detected") - - dir.join("Gemfile.lock").write <<~EOM - EOM - - out = exec_with_bash_functions <<~EOM - which_java() - { - return 1 - } - - if detect_needs_java "#{dir}"; then - echo "jruby detected" - else - echo "nope" - fi - EOM - - expect(out).to eq("nope") - end - end - - it "Detects java for jruby detection" do - Dir.mktmpdir do |dir| - dir = Pathname(dir) - dir.join("Gemfile.lock").write <<~EOM - RUBY VERSION - ruby 2.5.7p001 (jruby 9.2.13.0) - EOM - - out = exec_with_bash_functions <<~EOM - which_java() - { - return 0 - } - - if detect_needs_java "#{dir}"; then - echo "jruby detected" - else - echo "already installed" - fi - EOM - - expect(out).to eq("already installed") - end - end - - - def bash_functions_file - root_dir.join("bin", "support", "bash_functions.sh") - end - - def exec_with_bash_functions(code, stack: "heroku-18") - contents = <<~EOM - #! /usr/bin/env bash - set -eu - - STACK="#{stack}" - - #{bash_functions_file.read} - - #{code} - EOM - - file = Tempfile.new - file.write(contents) - file.close - FileUtils.chmod("+x", file.path) - - out = nil - success = false - begin - Timeout.timeout(60) do - out = `#{file.path} 2>&1`.strip - success = $?.success? - end - rescue Timeout::Error - out = "Command timed out" - success = false - end - unless success - message = <<~EOM - Contents: - - #{contents.lines.map.with_index { |line, number| " #{number.next} #{line.chomp}"}.join("\n") } - - Expected running script to succeed, but it did not - - Output: - - #{out} - EOM - - raise message - end - out - end -end