Something went wrong. Try again.
Main coves client
Something went wrong. Try again.
8.8 kB · 221 lines
Ruby
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222# frozen_string_literal: true
require "fileutils"require "json"require "minitest/autorun"require "open3"require "rbconfig"require "tmpdir"
# Exercise release preflight in an isolated fixture. Every external command is# replaced, and the first store lane always fails before any release mutation.class ReleaseTest < Minitest::Test def setup @directory = File.realpath(Dir.mktmpdir("coves-release-test")) %w[tool bin fake-bin release_notes android ios lib test].each do |directory| FileUtils.mkdir_p(File.join(@directory, directory)) end @log = File.join(@directory, "commands.jsonl") # Apple's system Ruby asks uname for the host architecture at startup. FileUtils.ln_s("/usr/bin/uname", File.join(@directory, "fake-bin/uname")) keytool = File.join(@directory, "fake-bin/keytool") File.write(keytool, "#!/bin/sh\nexit 0\n") File.chmod(0o755, keytool) source = File.read(File.expand_path("../../tool/release", __dir__)) source.sub!(/^KEYTOOL = .*$/, "KEYTOOL = #{keytool.inspect}") File.write(File.join(@directory, "tool/release"), source) File.write(File.join(@directory, "release_notes/1.2.3.txt"), "Release test fixture\n") File.write(File.join(@directory, "pubspec.yaml"), "version: 1.2.2+1\n")
executable("git", 'exit 0') executable("flutter", <<~'RUBY') if ARGV.first == "analyze" exit ENV.fetch("ANALYZE_STATUS", "0").to_i elsif ARGV.first == "test" # Dependency resolution and application logs are not reporter events. puts "Resolving dependencies..." puts '{application logger: not valid reporter JSON}' puts JSON.generate(type: "done", success: false) reporter = ARGV.find { |argument| argument.start_with?("--file-reporter=json:") } if reporter File.open(reporter.delete_prefix("--file-reporter=json:"), "w") do |report| report.puts "invalid report" if ENV["MALFORMED_TEST_REPORT"] == "1" report.puts "[]" if ENV["INVALID_EVENT_SHAPE"] == "1" unless ENV["EMPTY_TEST_SUITE"] == "1" report.puts JSON.generate(type: "testStart", test: {id: 1, name: "fixture test", metadata: {skip: false}}) report.puts JSON.generate(type: "testDone", testID: 1, result: "success", skipped: ENV["SKIPPED_TEST"] == "1", hidden: false) end report.puts JSON.generate(type: "done", success: ENV["FAILED_TEST_COMPLETION"] != "1") unless ENV["MISSING_TEST_COMPLETION"] == "1" end end exit 0 end abort "unexpected Flutter operation" RUBY executable("dart", 'exit ENV.fetch("FORMAT_STATUS", "0").to_i') executable("fastlane", 'abort "untrusted PATH fastlane invoked"') executable("bundle", 'exit ENV.fetch("BUNDLE_STATUS", "0").to_i') bundle_path = File.join(@directory, "fake-bin/bundle") hook = <<~RUBY require "rubygems" module FixtureBundlerPath def bin_path(name, executable = nil, *requirements) return #{bundle_path.inspect} if name == "bundler" && executable == "bundle" super end end Gem.singleton_class.prepend(FixtureBundlerPath) RUBY File.write(File.join(@directory, "bundle_hook.rb"), hook) # Intentionally no executable bit: the release must use its current Ruby. File.write(File.join(@directory, "bin/fastlane"), command_source('abort "fixture stops before store access"')) end
def teardown FileUtils.remove_entry(@directory) end
def test_uses_pinned_fastlane_under_current_ruby_after_all_checks output, status = release
refute status.success? assert_includes output, "fixture stops before store access" pinned = commands.find { |command| command.fetch("path") == File.join(@directory, "bin/fastlane") } refute_nil pinned, "must invoke project bin/fastlane, not PATH fastlane" assert_equal RbConfig.ruby, pinned.fetch("ruby") assert_equal ["store_state"], pinned.fetch("arguments") assert_equal File.join(@directory, "android"), pinned.fetch("directory") assert_equal %w[git bundle flutter dart flutter fastlane], commands.map { |command| File.basename(command.fetch("path")) } assert_equal ["check"], command("bundle").fetch("arguments") assert_equal RbConfig.ruby, command("bundle").fetch("ruby") assert_equal ["analyze"], command("flutter").fetch("arguments") assert_equal %w[format --output=none --set-exit-if-changed lib test], command("dart").fetch("arguments") test_arguments = commands.select { |entry| File.basename(entry.fetch("path")) == "flutter" }.last.fetch("arguments") assert test_arguments.any? { |argument| argument.start_with?("--file-reporter=json:") }, "must use a dedicated report file independent of application stdout" end
def test_missing_bundle_fails_with_recovery_before_store_access output, status = release("BUNDLE_STATUS" => "1")
refute status.success? assert_match(/bundle install/i, output) assert_no_store_access refute commands.any? { |entry| File.basename(entry.fetch("path")) == "flutter" } end
def test_analyzer_findings_prevent_store_access _output, status = release("ANALYZE_STATUS" => "1")
refute status.success? assert_equal ["analyze"], command("flutter").fetch("arguments") assert_no_store_access end
def test_formatter_drift_prevents_store_access_without_rewriting_files _output, status = release("FORMAT_STATUS" => "1")
refute status.success? formatter = command("dart") refute_nil formatter, "must check formatting before stores" assert_includes formatter.fetch("arguments"), "--output=none" assert_includes formatter.fetch("arguments"), "--set-exit-if-changed" assert_no_store_access end
def test_successful_flutter_exit_with_a_skipped_test_prevents_store_access output, status = release("SKIPPED_TEST" => "1")
refute status.success? assert_match(/skip/i, output) assert_no_store_access end
def test_incomplete_test_report_prevents_store_access _output, status = release("MISSING_TEST_COMPLETION" => "1")
refute status.success? assert_no_store_access end
def test_test_gate_cannot_be_bypassed output, status = release({}, "--skip-tests")
assert_equal 2, status.exitstatus assert_match(/usage/i, output) assert_empty commands end
def test_failed_completion_prevents_store_access_even_with_successful_process_exit _output, status = release("FAILED_TEST_COMPLETION" => "1")
refute status.success? assert_no_store_access end
def test_malformed_report_prevents_store_access output, status = release("MALFORMED_TEST_REPORT" => "1")
refute status.success? assert_match(/invalid machine report/i, output) assert_no_store_access end
def test_empty_suite_prevents_store_access _output, status = release("EMPTY_TEST_SUITE" => "1")
refute status.success? assert_no_store_access end
def test_invalid_event_shape_fails_gracefully_before_store_access output, status = release("INVALID_EVENT_SHAPE" => "1")
refute status.success? assert_match(/invalid machine report/i, output) refute_match(/TypeError|NoMethodError/, output) assert_no_store_access end
private
def command_source(body) <<~RUBY require "json" require "rbconfig" File.open(ENV.fetch("COMMAND_LOG"), "a") do |log| log.puts JSON.generate(path: File.expand_path($PROGRAM_NAME), arguments: ARGV, directory: Dir.pwd, ruby: RbConfig.ruby) end #{body} RUBY end
def executable(name, body) path = File.join(@directory, "fake-bin", name) File.write(path, "#!#{RbConfig.ruby}\n#{command_source(body)}") File.chmod(0o755, path) end
def release(overrides = {}, *arguments) # The fixture must also be isolated when this suite runs under bundle exec. environment = ENV.keys.select { |name| name.start_with?("GEM_", "RUBY", "BUNDLE_", "BUNDLER_") }.to_h { |name| [name, nil] }.merge({ "PATH" => File.join(@directory, "fake-bin"), "COMMAND_LOG" => @log, "RUBYOPT" => "-r#{File.join(@directory, 'bundle_hook.rb')}", "BUNDLE_GEMFILE" => nil, "ASC_KEY_ID" => "fixture", "ASC_ISSUER_ID" => "fixture", "ASC_KEY_PATH" => "fixture", }).merge(overrides) Open3.capture2e(environment, RbConfig.ruby, File.join(@directory, "tool/release"), "1.2.3", "--dry-run", *arguments, chdir: @directory) end
def commands File.exist?(@log) ? File.readlines(@log).map { |line| JSON.parse(line) } : [] end
def command(name) commands.find { |entry| File.basename(entry.fetch("path")) == name } end
def assert_no_store_access refute commands.any? { |entry| File.basename(entry.fetch("path")) == "fastlane" }, "must fail before any fastlane lane" endend