From 7facca488f84e2d2cf0d6a3c8f601a1f21d0f714 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:41:47 -0400 Subject: [PATCH 1/5] build(homebrew): reduce test duplication --- .github/workflows/ci-homebrew.yml | 3 +- packaging/sunshine.rb | 94 ++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci-homebrew.yml b/.github/workflows/ci-homebrew.yml index 1cc07d38ba8..8f6ad376782 100644 --- a/.github/workflows/ci-homebrew.yml +++ b/.github/workflows/ci-homebrew.yml @@ -177,7 +177,8 @@ jobs: - name: Validate Homebrew Formula id: test if: matrix.release != true - uses: LizardByte/actions/actions/release_homebrew@200eaeb897a2b065a65cb6f16b41077432007490 # v2026.605.34721 + # uses: LizardByte/actions/actions/release_homebrew@200eaeb897a2b065a65cb6f16b41077432007490 # v2026.605.34721 + uses: LizardByte/actions/actions/release_homebrew@fix/release_homebrew/reduce-test-duplication with: actionlint_config: "---\n# empty config" formula_file: ${{ github.workspace }}/homebrew/sunshine.rb diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 48d7f682073..c51137e9585 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -48,7 +48,6 @@ class Sunshine < Formula depends_on "graphviz" => :build if build.with? "docs" depends_on "node" => :build depends_on "pkgconf" => :build - depends_on "gcovr" => :test depends_on "boost" depends_on "curl" depends_on "icu4c@78" @@ -62,6 +61,7 @@ class Sunshine < Formula on_linux do depends_on GCC_FORMULA => [:build, :test] + depends_on "gcovr" => [:build, :test] depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda" depends_on "python3" => :build depends_on "at-spi2-core" @@ -248,6 +248,62 @@ def configure_cuda(args) ohai "CUDA enabled with nvcc at: #{nvcc_path}" end + def release_homebrew_testpath + testpath_value = ENV.fetch("HOMEBREW_TEST_ARTIFACTS_DIR", "") + return Pathname.new(testpath_value) unless testpath_value.empty? + + return if ENV.fetch("HOMEBREW_TEST_BOT", "") != "1" + + temp_path = ENV.fetch("HOMEBREW_TEMP", "") + return if temp_path.empty? + + Pathname.new(temp_path)/name/"test" + end + + def ensure_artifact_exists(path) + odie "#{path} was not created" unless path.exist? + end + + def run_test_suite(artifact_dir) + mkdir_p artifact_dir/"tests" + test_results = artifact_dir/"tests/test_results.xml" + + system bin/"test_sunshine", "--gtest_color=yes", "--gtest_output=xml:#{test_results}" + ensure_artifact_exists test_results + end + + def generate_coverage_report(artifact_dir, coverage_buildpath) + return unless OS.linux? + return if coverage_buildpath.to_s.empty? + + coverage_report = artifact_dir/"coverage.xml" + + cd "#{coverage_buildpath}/build" do + gcc_path = Formula[GCC_FORMULA] + gcov_executable = "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}" + + system "gcovr", ".", + "-r", "../src", + "--gcov-executable", gcov_executable, + "--exclude-noncode-lines", + "--exclude-throw-branches", + "--exclude-unreachable-branches", + "--xml-pretty", + "-o=#{coverage_report}" + end + + ensure_artifact_exists coverage_report + end + + def collect_test_artifacts + artifact_dir = release_homebrew_testpath + return unless IS_UPSTREAM_REPO + return unless artifact_dir + + run_test_suite artifact_dir + generate_coverage_report artifact_dir, buildpath + end + def build_cmake_args args = base_cmake_args add_test_args(args) @@ -279,6 +335,7 @@ def install setup_build_environment build_and_install_project install_platform_specific_files + collect_test_artifacts end service do @@ -314,33 +371,14 @@ def caveats # test that the binary runs at all system bin/"sunshine", "--version" - if IS_UPSTREAM_REPO && ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true" - # run the test suite - system bin/"test_sunshine", "--gtest_color=yes", "--gtest_output=xml:tests/test_results.xml" - assert_path_exists File.join(testpath, "tests", "test_results.xml") - - # create gcovr report - buildpath = ENV.fetch("HOMEBREW_BUILDPATH", "") - unless buildpath.empty? - # Change to the source directory for gcovr to work properly - cd "#{buildpath}/build" do - # Use GCC version to match what was used during compilation - if OS.linux? - gcc_path = Formula[GCC_FORMULA] - gcov_executable = "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}" - - system "gcovr", ".", - "-r", "../src", - "--gcov-executable", gcov_executable, - "--exclude-noncode-lines", - "--exclude-throw-branches", - "--exclude-unreachable-branches", - "--xml-pretty", - "-o=#{testpath}/coverage.xml" - - assert_path_exists File.join(testpath, "coverage.xml") - end - end + if IS_UPSTREAM_REPO + artifact_dir = release_homebrew_testpath + if artifact_dir + assert_path_exists artifact_dir/"tests/test_results.xml" + assert_path_exists artifact_dir/"coverage.xml" if OS.linux? + elsif ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true" + run_test_suite testpath + generate_coverage_report testpath, ENV.fetch("HOMEBREW_BUILDPATH", "") end end end From 2dd7923f54d295f26c11778b0aa54c76a9eccfd9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:08:17 -0400 Subject: [PATCH 2/5] Update release_homebrew action reference Pin LizardByte/actions/actions/release_homebrew to commit a46850981292c4bbc0c41715f286ad95adaaaf4a (v2026.625.20301) in CI workflows. Replaces previous refs (200eaeb897a2b065a65cb6f16b41077432007490 and fix/release_homebrew/reduce-test-duplication) used by the Validate Homebrew Formula and Upload Homebrew Beta Formula jobs. --- .github/workflows/ci-homebrew.yml | 3 +-- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-homebrew.yml b/.github/workflows/ci-homebrew.yml index 8f6ad376782..e63ba9c6044 100644 --- a/.github/workflows/ci-homebrew.yml +++ b/.github/workflows/ci-homebrew.yml @@ -177,8 +177,7 @@ jobs: - name: Validate Homebrew Formula id: test if: matrix.release != true - # uses: LizardByte/actions/actions/release_homebrew@200eaeb897a2b065a65cb6f16b41077432007490 # v2026.605.34721 - uses: LizardByte/actions/actions/release_homebrew@fix/release_homebrew/reduce-test-duplication + uses: LizardByte/actions/actions/release_homebrew@a46850981292c4bbc0c41715f286ad95adaaaf4a # v2026.625.20301 with: actionlint_config: "---\n# empty config" formula_file: ${{ github.workspace }}/homebrew/sunshine.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6de425f85d..f861bb7efad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,7 +343,7 @@ jobs: path: homebrew - name: Upload Homebrew Beta Formula - uses: LizardByte/actions/actions/release_homebrew@200eaeb897a2b065a65cb6f16b41077432007490 # v2026.605.34721 + uses: LizardByte/actions/actions/release_homebrew@a46850981292c4bbc0c41715f286ad95adaaaf4a # v2026.625.20301 with: actionlint_config: "---\n# empty config" formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb From 7188c6d8cc8a29f5501aa4c14f29661ca5543d81 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:09:15 -0400 Subject: [PATCH 3/5] Enable coverage on macOS and unify gcovr handling Enable coverage reporting for Homebrew macOS CI jobs and consolidate gcovr usage in the Homebrew formula. Move gcovr to a top-level dependency, remove the duplicated on_linux entry, and add coverage_gcov_executable to pick llvm-cov's gcov on macOS or the GCC gcov on Linux. generate_coverage_report now uses the selected gcov executable and no longer skips macOS; the test harness also always asserts the presence of coverage.xml. Changes affect .github/workflows/ci.yml and packaging/sunshine.rb. --- .github/workflows/ci.yml | 6 +++--- packaging/sunshine.rb | 34 +++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f861bb7efad..2c2cc346772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,13 +218,13 @@ jobs: coverage: true pr: true - name: Homebrew-macos-14 - coverage: false + coverage: true pr: true - name: Homebrew-macos-15 - coverage: false + coverage: true pr: true - name: Homebrew-macos-26 - coverage: false + coverage: true pr: true - name: Homebrew-ubuntu-24.04 coverage: true diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index c51137e9585..0b8f26ec180 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -7,6 +7,7 @@ class Sunshine < Formula CUDA_FORMULA = "cuda@#{CUDA_VERSION}".freeze GCC_VERSION = "14".freeze GCC_FORMULA = "gcc@#{GCC_VERSION}".freeze + GCOV_IGNORE_ERRORS_FLAG = "--gcov-ignore-errors".freeze IS_UPSTREAM_REPO = ENV.fetch("GITHUB_REPOSITORY", "") == "LizardByte/Sunshine" desc "@PROJECT_DESCRIPTION@" @@ -45,6 +46,7 @@ class Sunshine < Formula depends_on "cmake" => :build depends_on "doxygen" => :build if build.with? "docs" + depends_on "gcovr" => [:build, :test] depends_on "graphviz" => :build if build.with? "docs" depends_on "node" => :build depends_on "pkgconf" => :build @@ -61,7 +63,6 @@ class Sunshine < Formula on_linux do depends_on GCC_FORMULA => [:build, :test] - depends_on "gcovr" => [:build, :test] depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda" depends_on "python3" => :build depends_on "at-spi2-core" @@ -272,19 +273,38 @@ def run_test_suite(artifact_dir) ensure_artifact_exists test_results end + def coverage_gcov_executable + if OS.mac? + llvm_path = Formula["llvm"] + "#{llvm_path.opt_bin}/llvm-cov gcov" + else + gcc_path = Formula[GCC_FORMULA] + "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}" + end + end + + def coverage_gcov_options + options = ["--gcov-executable", coverage_gcov_executable] + if OS.mac? + options += [ + GCOV_IGNORE_ERRORS_FLAG, "source_not_found", + GCOV_IGNORE_ERRORS_FLAG, "output_error", + GCOV_IGNORE_ERRORS_FLAG, "no_working_dir_found" + ] + end + + options + end + def generate_coverage_report(artifact_dir, coverage_buildpath) - return unless OS.linux? return if coverage_buildpath.to_s.empty? coverage_report = artifact_dir/"coverage.xml" cd "#{coverage_buildpath}/build" do - gcc_path = Formula[GCC_FORMULA] - gcov_executable = "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}" - system "gcovr", ".", "-r", "../src", - "--gcov-executable", gcov_executable, + *coverage_gcov_options, "--exclude-noncode-lines", "--exclude-throw-branches", "--exclude-unreachable-branches", @@ -375,7 +395,7 @@ def caveats artifact_dir = release_homebrew_testpath if artifact_dir assert_path_exists artifact_dir/"tests/test_results.xml" - assert_path_exists artifact_dir/"coverage.xml" if OS.linux? + assert_path_exists artifact_dir/"coverage.xml" elsif ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true" run_test_suite testpath generate_coverage_report testpath, ENV.fetch("HOMEBREW_BUILDPATH", "") From 2ae3d9d385267b7a894daffef6897afd52e60ed8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:17:33 -0400 Subject: [PATCH 4/5] Add LLVM-based coverage for macOS tests Enable source-based LLVM coverage on macOS and unify test binary handling. Adds SUNSHINE_LLVM_COVERAGE CMake option and LLVM compile/link flags, introduces LLVM_PROFILE_FILE handling, llvm-profdata discovery, and gcovr invocation path for macOS (.profraw -> llvm-profdata flow). Factors out TEST_BINARY constant, updates test invocation and install to use it, and adds checks to ensure profile data and coverage lines exist. Removes the previous macOS llvm formula dependency and replaces gcov-specific options with separate LLVM vs GCC coverage code paths. --- packaging/sunshine.rb | 95 +++++++++++++++++++++++++++++-------------- tests/CMakeLists.txt | 10 ++++- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 0b8f26ec180..198669e25dc 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -7,7 +7,8 @@ class Sunshine < Formula CUDA_FORMULA = "cuda@#{CUDA_VERSION}".freeze GCC_VERSION = "14".freeze GCC_FORMULA = "gcc@#{GCC_VERSION}".freeze - GCOV_IGNORE_ERRORS_FLAG = "--gcov-ignore-errors".freeze + LLVM_PROFILE_FILE_ENV = "LLVM_PROFILE_FILE".freeze + TEST_BINARY = "test_sunshine".freeze IS_UPSTREAM_REPO = ENV.fetch("GITHUB_REPOSITORY", "") == "LizardByte/Sunshine" desc "@PROJECT_DESCRIPTION@" @@ -57,10 +58,6 @@ class Sunshine < Formula depends_on "openssl@3" depends_on "opus" - on_macos do - depends_on "llvm" => [:build, :test] - end - on_linux do depends_on GCC_FORMULA => [:build, :test] depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda" @@ -184,6 +181,7 @@ def base_cmake_args def add_test_args(args) if IS_UPSTREAM_REPO args << "-DBUILD_TESTS=ON" + args << "-DSUNSHINE_LLVM_COVERAGE=ON" if OS.mac? ohai "Building tests: enabled" else args << "-DBUILD_TESTS=OFF" @@ -269,31 +267,53 @@ def run_test_suite(artifact_dir) mkdir_p artifact_dir/"tests" test_results = artifact_dir/"tests/test_results.xml" - system bin/"test_sunshine", "--gtest_color=yes", "--gtest_output=xml:#{test_results}" + if OS.mac? + with_llvm_profile_file(artifact_dir) do + system bin/TEST_BINARY, "--gtest_color=yes", "--gtest_output=xml:#{test_results}" + end + else + system bin/TEST_BINARY, "--gtest_color=yes", "--gtest_output=xml:#{test_results}" + end + ensure_artifact_exists test_results end - def coverage_gcov_executable - if OS.mac? - llvm_path = Formula["llvm"] - "#{llvm_path.opt_bin}/llvm-cov gcov" + def with_llvm_profile_file(artifact_dir) + original_profile_file = ENV[LLVM_PROFILE_FILE_ENV] + ENV[LLVM_PROFILE_FILE_ENV] = "#{artifact_dir}/sunshine-%p.profraw" + yield + ensure + if original_profile_file + ENV[LLVM_PROFILE_FILE_ENV] = original_profile_file else - gcc_path = Formula[GCC_FORMULA] - "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}" + ENV.delete(LLVM_PROFILE_FILE_ENV) end end def coverage_gcov_options - options = ["--gcov-executable", coverage_gcov_executable] - if OS.mac? - options += [ - GCOV_IGNORE_ERRORS_FLAG, "source_not_found", - GCOV_IGNORE_ERRORS_FLAG, "output_error", - GCOV_IGNORE_ERRORS_FLAG, "no_working_dir_found" - ] - end + gcc_path = Formula[GCC_FORMULA] + ["--gcov-executable", "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}"] + end - options + def llvm_profdata_executable + Utils.safe_popen_read("xcrun", "--find", "llvm-profdata").strip + end + + def coverage_llvm_options + [ + "--llvm-profdata-executable", llvm_profdata_executable, + "--llvm-cov-binary", bin/TEST_BINARY + ] + end + + def coverage_common_options(coverage_report) + [ + "--exclude-noncode-lines", + "--exclude-throw-branches", + "--exclude-unreachable-branches", + "--xml-pretty", + "-o=#{coverage_report}", + ] end def generate_coverage_report(artifact_dir, coverage_buildpath) @@ -301,18 +321,31 @@ def generate_coverage_report(artifact_dir, coverage_buildpath) coverage_report = artifact_dir/"coverage.xml" - cd "#{coverage_buildpath}/build" do - system "gcovr", ".", - "-r", "../src", - *coverage_gcov_options, - "--exclude-noncode-lines", - "--exclude-throw-branches", - "--exclude-unreachable-branches", - "--xml-pretty", - "-o=#{coverage_report}" + if OS.mac? + odie "No LLVM profile data was created" if Dir["#{artifact_dir}/*.profraw"].empty? + + cd coverage_buildpath.to_s do + system "gcovr", artifact_dir.to_s, + "-r", "src", + *coverage_llvm_options, + *coverage_common_options(coverage_report) + end + else + cd "#{coverage_buildpath}/build" do + system "gcovr", ".", + "-r", "../src", + *coverage_gcov_options, + *coverage_common_options(coverage_report) + end end ensure_artifact_exists coverage_report + ensure_coverage_report_has_lines coverage_report + end + + def ensure_coverage_report_has_lines(path) + lines_valid = path.read[/lines-valid="(\d+)"/, 1].to_i + odie "#{path} does not contain any source lines" if lines_valid.zero? end def collect_test_artifacts @@ -343,7 +376,7 @@ def build_and_install_project end def install_platform_specific_files - bin.install "build/tests/test_sunshine" if IS_UPSTREAM_REPO + bin.install "build/tests/#{TEST_BINARY}" if IS_UPSTREAM_REPO # codesign the binary on intel macs system "codesign", "-s", "-", "--force", "--deep", bin/"sunshine" if OS.mac? && Hardware::CPU.intel? diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 984481f6010..c38547f4d57 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,8 +23,14 @@ include_directories("${GTEST_SOURCE_DIR}/googletest/include" "${GTEST_SOURCE_DIR # coverage # https://gcovr.com/en/stable/guide/compiling.html#compiler-options -set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") -set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") +option(SUNSHINE_LLVM_COVERAGE "Use LLVM source-based coverage" OFF) +if(SUNSHINE_LLVM_COVERAGE) + add_compile_options(-fprofile-instr-generate -fcoverage-mapping -ggdb -O0) + add_link_options(-fprofile-instr-generate) +else() + set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") + set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0") +endif() # Find the correct libgcov library path matching the gcc compiler version # This ensures the test executable links against the same libgcov version used during compilation From a02a5531f53cb5b349b6f41eefb5e2fc9185e7aa Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:49:20 -0400 Subject: [PATCH 5/5] Add lcov coverage support and CI updates Update CI to upload and consume lcov coverage output and make coverage file configurable per matrix. The workflows now include coverage.lcov in artifacts and expose a matrix coverage_file used by the Codecov step. Refactor packaging/sunshine.rb to support generating LLVM-based lcov on macOS and keep gcov/xml generation on Linux: add constants, new helpers to merge profraw into profdata, export llvm-cov lcov, rewrite source paths to be relative to src/, and validate lcov output. Adjust artifact handling to expect coverage.lcov on macOS and coverage.xml on Linux and update assertions accordingly. --- .github/workflows/ci-homebrew.yml | 1 + .github/workflows/ci.yml | 5 +- packaging/sunshine.rb | 101 ++++++++++++++++++++++-------- 3 files changed, 81 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci-homebrew.yml b/.github/workflows/ci-homebrew.yml index e63ba9c6044..54e28ef28df 100644 --- a/.github/workflows/ci-homebrew.yml +++ b/.github/workflows/ci-homebrew.yml @@ -198,6 +198,7 @@ jobs: name: coverage-Homebrew-${{ matrix.os_name }}-${{ matrix.os_version }} path: | ${{ steps.test.outputs.testpath }}/coverage.xml + ${{ steps.test.outputs.testpath }}/coverage.lcov ${{ steps.test.outputs.testpath }}/tests/test_results.xml if-no-files-found: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c2cc346772..f7ee8d8afb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,12 +219,15 @@ jobs: pr: true - name: Homebrew-macos-14 coverage: true + coverage_file: coverage.lcov pr: true - name: Homebrew-macos-15 coverage: true + coverage_file: coverage.lcov pr: true - name: Homebrew-macos-26 coverage: true + coverage_file: coverage.lcov pr: true - name: Homebrew-ubuntu-24.04 coverage: true @@ -264,7 +267,7 @@ jobs: with: disable_search: true fail_ci_if_error: true - files: ./_coverage/coverage.xml + files: ./_coverage/${{ matrix.coverage_file || 'coverage.xml' }} report_type: coverage flags: ${{ matrix.name }} token: ${{ secrets.CODECOV_TOKEN }} diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 198669e25dc..08f68709f80 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -5,6 +5,9 @@ class Sunshine < Formula CUDA_VERSION = "13.1".freeze CUDA_FORMULA = "cuda@#{CUDA_VERSION}".freeze + COVERAGE_LCOV = "coverage.lcov".freeze + COVERAGE_PROFDATA = "coverage.profdata".freeze + COVERAGE_XML = "coverage.xml".freeze GCC_VERSION = "14".freeze GCC_FORMULA = "gcc@#{GCC_VERSION}".freeze LLVM_PROFILE_FILE_ENV = "LLVM_PROFILE_FILE".freeze @@ -47,7 +50,6 @@ class Sunshine < Formula depends_on "cmake" => :build depends_on "doxygen" => :build if build.with? "docs" - depends_on "gcovr" => [:build, :test] depends_on "graphviz" => :build if build.with? "docs" depends_on "node" => :build depends_on "pkgconf" => :build @@ -60,6 +62,7 @@ class Sunshine < Formula on_linux do depends_on GCC_FORMULA => [:build, :test] + depends_on "gcovr" => [:build, :test] depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda" depends_on "python3" => :build depends_on "at-spi2-core" @@ -279,7 +282,7 @@ def run_test_suite(artifact_dir) end def with_llvm_profile_file(artifact_dir) - original_profile_file = ENV[LLVM_PROFILE_FILE_ENV] + original_profile_file = ENV.fetch(LLVM_PROFILE_FILE_ENV, nil) ENV[LLVM_PROFILE_FILE_ENV] = "#{artifact_dir}/sunshine-%p.profraw" yield ensure @@ -299,11 +302,12 @@ def llvm_profdata_executable Utils.safe_popen_read("xcrun", "--find", "llvm-profdata").strip end - def coverage_llvm_options - [ - "--llvm-profdata-executable", llvm_profdata_executable, - "--llvm-cov-binary", bin/TEST_BINARY - ] + def llvm_cov_executable + Utils.safe_popen_read("xcrun", "--find", "llvm-cov").strip + end + + def coverage_report_path(artifact_dir) + artifact_dir/(OS.mac? ? COVERAGE_LCOV : COVERAGE_XML) end def coverage_common_options(coverage_report) @@ -319,35 +323,82 @@ def coverage_common_options(coverage_report) def generate_coverage_report(artifact_dir, coverage_buildpath) return if coverage_buildpath.to_s.empty? - coverage_report = artifact_dir/"coverage.xml" + coverage_report = coverage_report_path(artifact_dir) if OS.mac? - odie "No LLVM profile data was created" if Dir["#{artifact_dir}/*.profraw"].empty? - - cd coverage_buildpath.to_s do - system "gcovr", artifact_dir.to_s, - "-r", "src", - *coverage_llvm_options, - *coverage_common_options(coverage_report) - end + generate_llvm_coverage_report artifact_dir, coverage_buildpath, coverage_report else - cd "#{coverage_buildpath}/build" do - system "gcovr", ".", - "-r", "../src", - *coverage_gcov_options, - *coverage_common_options(coverage_report) - end + generate_gcov_coverage_report coverage_report, coverage_buildpath end ensure_artifact_exists coverage_report - ensure_coverage_report_has_lines coverage_report end - def ensure_coverage_report_has_lines(path) + def generate_llvm_coverage_report(artifact_dir, coverage_buildpath, coverage_report) + profile_files = Dir["#{artifact_dir}/*.profraw"] + odie "No LLVM profile data was created" if profile_files.empty? + + profile_data = artifact_dir/COVERAGE_PROFDATA + system llvm_profdata_executable, "merge", "--sparse", "-o", profile_data.to_s, *profile_files + lcov = Utils.safe_popen_read( + llvm_cov_executable, + "export", + "-format=lcov", + "-instr-profile=#{profile_data}", + (bin/TEST_BINARY).to_s, + ) + coverage_report.write lcov_for_source_files(lcov, coverage_buildpath) + ensure_lcov_report_has_lines coverage_report + end + + def generate_gcov_coverage_report(coverage_report, coverage_buildpath) + cd "#{coverage_buildpath}/build" do + system "gcovr", ".", + "-r", "../src", + *coverage_gcov_options, + *coverage_common_options(coverage_report) + end + + ensure_cobertura_report_has_lines coverage_report + end + + def coverage_source_prefixes(coverage_buildpath) + paths = [ + coverage_buildpath.to_s, + Pathname.new(coverage_buildpath.to_s).realpath.to_s, + ] + paths.uniq.map { |path| "#{path}/src/" } + end + + def relative_lcov_record(record, source_prefixes) + lines = record.lines + source_index = lines.index { |line| line.start_with?("SF:") } + return unless source_index + + source_path = lines[source_index].delete_prefix("SF:").strip + source_prefix = source_prefixes.find { |prefix| source_path.start_with?(prefix) } + return unless source_prefix + + lines[source_index] = "SF:src/#{source_path.delete_prefix(source_prefix)}\n" + "#{lines.join}end_of_record\n" + end + + def lcov_for_source_files(lcov, coverage_buildpath) + source_prefixes = coverage_source_prefixes(coverage_buildpath) + records = lcov.split("end_of_record\n") + records.filter_map { |record| relative_lcov_record(record, source_prefixes) }.join + end + + def ensure_cobertura_report_has_lines(path) lines_valid = path.read[/lines-valid="(\d+)"/, 1].to_i odie "#{path} does not contain any source lines" if lines_valid.zero? end + def ensure_lcov_report_has_lines(path) + has_lines = path.read.lines.any? { |line| line.start_with?("DA:") } + odie "#{path} does not contain any source lines" unless has_lines + end + def collect_test_artifacts artifact_dir = release_homebrew_testpath return unless IS_UPSTREAM_REPO @@ -428,7 +479,7 @@ def caveats artifact_dir = release_homebrew_testpath if artifact_dir assert_path_exists artifact_dir/"tests/test_results.xml" - assert_path_exists artifact_dir/"coverage.xml" + assert_path_exists coverage_report_path(artifact_dir) elsif ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true" run_test_suite testpath generate_coverage_report testpath, ENV.fetch("HOMEBREW_BUILDPATH", "")