build(homebrew): reduce test duplication and add test coverage for macOS (#5331)
This commit is contained in:
3
.github/workflows/ci-homebrew.yml
vendored
3
.github/workflows/ci-homebrew.yml
vendored
@@ -177,7 +177,7 @@ jobs:
|
|||||||
- name: Validate Homebrew Formula
|
- name: Validate Homebrew Formula
|
||||||
id: test
|
id: test
|
||||||
if: matrix.release != true
|
if: matrix.release != true
|
||||||
uses: LizardByte/actions/actions/release_homebrew@200eaeb897a2b065a65cb6f16b41077432007490 # v2026.605.34721
|
uses: LizardByte/actions/actions/release_homebrew@a46850981292c4bbc0c41715f286ad95adaaaf4a # v2026.625.20301
|
||||||
with:
|
with:
|
||||||
actionlint_config: "---\n# empty config"
|
actionlint_config: "---\n# empty config"
|
||||||
formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
|
formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
|
||||||
@@ -198,6 +198,7 @@ jobs:
|
|||||||
name: coverage-Homebrew-${{ matrix.os_name }}-${{ matrix.os_version }}
|
name: coverage-Homebrew-${{ matrix.os_name }}-${{ matrix.os_version }}
|
||||||
path: |
|
path: |
|
||||||
${{ steps.test.outputs.testpath }}/coverage.xml
|
${{ steps.test.outputs.testpath }}/coverage.xml
|
||||||
|
${{ steps.test.outputs.testpath }}/coverage.lcov
|
||||||
${{ steps.test.outputs.testpath }}/tests/test_results.xml
|
${{ steps.test.outputs.testpath }}/tests/test_results.xml
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|||||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -218,13 +218,16 @@ jobs:
|
|||||||
coverage: true
|
coverage: true
|
||||||
pr: true
|
pr: true
|
||||||
- name: Homebrew-macos-14
|
- name: Homebrew-macos-14
|
||||||
coverage: false
|
coverage: true
|
||||||
|
coverage_file: coverage.lcov
|
||||||
pr: true
|
pr: true
|
||||||
- name: Homebrew-macos-15
|
- name: Homebrew-macos-15
|
||||||
coverage: false
|
coverage: true
|
||||||
|
coverage_file: coverage.lcov
|
||||||
pr: true
|
pr: true
|
||||||
- name: Homebrew-macos-26
|
- name: Homebrew-macos-26
|
||||||
coverage: false
|
coverage: true
|
||||||
|
coverage_file: coverage.lcov
|
||||||
pr: true
|
pr: true
|
||||||
- name: Homebrew-ubuntu-24.04
|
- name: Homebrew-ubuntu-24.04
|
||||||
coverage: true
|
coverage: true
|
||||||
@@ -264,7 +267,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
disable_search: true
|
disable_search: true
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
files: ./_coverage/coverage.xml
|
files: ./_coverage/${{ matrix.coverage_file || 'coverage.xml' }}
|
||||||
report_type: coverage
|
report_type: coverage
|
||||||
flags: ${{ matrix.name }}
|
flags: ${{ matrix.name }}
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@@ -343,7 +346,7 @@ jobs:
|
|||||||
path: homebrew
|
path: homebrew
|
||||||
|
|
||||||
- name: Upload Homebrew Beta Formula
|
- 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:
|
with:
|
||||||
actionlint_config: "---\n# empty config"
|
actionlint_config: "---\n# empty config"
|
||||||
formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb
|
formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ class Sunshine < Formula
|
|||||||
|
|
||||||
CUDA_VERSION = "13.1".freeze
|
CUDA_VERSION = "13.1".freeze
|
||||||
CUDA_FORMULA = "cuda@#{CUDA_VERSION}".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_VERSION = "14".freeze
|
||||||
GCC_FORMULA = "gcc@#{GCC_VERSION}".freeze
|
GCC_FORMULA = "gcc@#{GCC_VERSION}".freeze
|
||||||
|
LLVM_PROFILE_FILE_ENV = "LLVM_PROFILE_FILE".freeze
|
||||||
|
TEST_BINARY = "test_sunshine".freeze
|
||||||
IS_UPSTREAM_REPO = ENV.fetch("GITHUB_REPOSITORY", "") == "LizardByte/Sunshine"
|
IS_UPSTREAM_REPO = ENV.fetch("GITHUB_REPOSITORY", "") == "LizardByte/Sunshine"
|
||||||
|
|
||||||
desc "@PROJECT_DESCRIPTION@"
|
desc "@PROJECT_DESCRIPTION@"
|
||||||
@@ -48,7 +53,6 @@ class Sunshine < Formula
|
|||||||
depends_on "graphviz" => :build if build.with? "docs"
|
depends_on "graphviz" => :build if build.with? "docs"
|
||||||
depends_on "node" => :build
|
depends_on "node" => :build
|
||||||
depends_on "pkgconf" => :build
|
depends_on "pkgconf" => :build
|
||||||
depends_on "gcovr" => :test
|
|
||||||
depends_on "boost"
|
depends_on "boost"
|
||||||
depends_on "curl"
|
depends_on "curl"
|
||||||
depends_on "icu4c@78"
|
depends_on "icu4c@78"
|
||||||
@@ -56,12 +60,9 @@ class Sunshine < Formula
|
|||||||
depends_on "openssl@3"
|
depends_on "openssl@3"
|
||||||
depends_on "opus"
|
depends_on "opus"
|
||||||
|
|
||||||
on_macos do
|
|
||||||
depends_on "llvm" => [:build, :test]
|
|
||||||
end
|
|
||||||
|
|
||||||
on_linux do
|
on_linux do
|
||||||
depends_on GCC_FORMULA => [:build, :test]
|
depends_on GCC_FORMULA => [:build, :test]
|
||||||
|
depends_on "gcovr" => [:build, :test]
|
||||||
depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda"
|
depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda"
|
||||||
depends_on "python3" => :build
|
depends_on "python3" => :build
|
||||||
depends_on "at-spi2-core"
|
depends_on "at-spi2-core"
|
||||||
@@ -183,6 +184,7 @@ class Sunshine < Formula
|
|||||||
def add_test_args(args)
|
def add_test_args(args)
|
||||||
if IS_UPSTREAM_REPO
|
if IS_UPSTREAM_REPO
|
||||||
args << "-DBUILD_TESTS=ON"
|
args << "-DBUILD_TESTS=ON"
|
||||||
|
args << "-DSUNSHINE_LLVM_COVERAGE=ON" if OS.mac?
|
||||||
ohai "Building tests: enabled"
|
ohai "Building tests: enabled"
|
||||||
else
|
else
|
||||||
args << "-DBUILD_TESTS=OFF"
|
args << "-DBUILD_TESTS=OFF"
|
||||||
@@ -248,6 +250,164 @@ class Sunshine < Formula
|
|||||||
ohai "CUDA enabled with nvcc at: #{nvcc_path}"
|
ohai "CUDA enabled with nvcc at: #{nvcc_path}"
|
||||||
end
|
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"
|
||||||
|
|
||||||
|
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 with_llvm_profile_file(artifact_dir)
|
||||||
|
original_profile_file = ENV.fetch(LLVM_PROFILE_FILE_ENV, nil)
|
||||||
|
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
|
||||||
|
ENV.delete(LLVM_PROFILE_FILE_ENV)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def coverage_gcov_options
|
||||||
|
gcc_path = Formula[GCC_FORMULA]
|
||||||
|
["--gcov-executable", "#{gcc_path.opt_bin}/gcov-#{GCC_VERSION}"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def llvm_profdata_executable
|
||||||
|
Utils.safe_popen_read("xcrun", "--find", "llvm-profdata").strip
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
[
|
||||||
|
"--exclude-noncode-lines",
|
||||||
|
"--exclude-throw-branches",
|
||||||
|
"--exclude-unreachable-branches",
|
||||||
|
"--xml-pretty",
|
||||||
|
"-o=#{coverage_report}",
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_coverage_report(artifact_dir, coverage_buildpath)
|
||||||
|
return if coverage_buildpath.to_s.empty?
|
||||||
|
|
||||||
|
coverage_report = coverage_report_path(artifact_dir)
|
||||||
|
|
||||||
|
if OS.mac?
|
||||||
|
generate_llvm_coverage_report artifact_dir, coverage_buildpath, coverage_report
|
||||||
|
else
|
||||||
|
generate_gcov_coverage_report coverage_report, coverage_buildpath
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure_artifact_exists coverage_report
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
return unless artifact_dir
|
||||||
|
|
||||||
|
run_test_suite artifact_dir
|
||||||
|
generate_coverage_report artifact_dir, buildpath
|
||||||
|
end
|
||||||
|
|
||||||
def build_cmake_args
|
def build_cmake_args
|
||||||
args = base_cmake_args
|
args = base_cmake_args
|
||||||
add_test_args(args)
|
add_test_args(args)
|
||||||
@@ -267,7 +427,7 @@ class Sunshine < Formula
|
|||||||
end
|
end
|
||||||
|
|
||||||
def install_platform_specific_files
|
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
|
# codesign the binary on intel macs
|
||||||
system "codesign", "-s", "-", "--force", "--deep", bin/"sunshine" if OS.mac? && Hardware::CPU.intel?
|
system "codesign", "-s", "-", "--force", "--deep", bin/"sunshine" if OS.mac? && Hardware::CPU.intel?
|
||||||
@@ -279,6 +439,7 @@ class Sunshine < Formula
|
|||||||
setup_build_environment
|
setup_build_environment
|
||||||
build_and_install_project
|
build_and_install_project
|
||||||
install_platform_specific_files
|
install_platform_specific_files
|
||||||
|
collect_test_artifacts
|
||||||
end
|
end
|
||||||
|
|
||||||
service do
|
service do
|
||||||
@@ -314,33 +475,14 @@ class Sunshine < Formula
|
|||||||
# test that the binary runs at all
|
# test that the binary runs at all
|
||||||
system bin/"sunshine", "--version"
|
system bin/"sunshine", "--version"
|
||||||
|
|
||||||
if IS_UPSTREAM_REPO && ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true"
|
if IS_UPSTREAM_REPO
|
||||||
# run the test suite
|
artifact_dir = release_homebrew_testpath
|
||||||
system bin/"test_sunshine", "--gtest_color=yes", "--gtest_output=xml:tests/test_results.xml"
|
if artifact_dir
|
||||||
assert_path_exists File.join(testpath, "tests", "test_results.xml")
|
assert_path_exists artifact_dir/"tests/test_results.xml"
|
||||||
|
assert_path_exists coverage_report_path(artifact_dir)
|
||||||
# create gcovr report
|
elsif ENV.fetch("HOMEBREW_BOTTLE_BUILD", "false") != "true"
|
||||||
buildpath = ENV.fetch("HOMEBREW_BUILDPATH", "")
|
run_test_suite testpath
|
||||||
unless buildpath.empty?
|
generate_coverage_report testpath, ENV.fetch("HOMEBREW_BUILDPATH", "")
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ include_directories("${GTEST_SOURCE_DIR}/googletest/include" "${GTEST_SOURCE_DIR
|
|||||||
|
|
||||||
# coverage
|
# coverage
|
||||||
# https://gcovr.com/en/stable/guide/compiling.html#compiler-options
|
# https://gcovr.com/en/stable/guide/compiling.html#compiler-options
|
||||||
set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0")
|
option(SUNSHINE_LLVM_COVERAGE "Use LLVM source-based coverage" OFF)
|
||||||
set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage -ggdb -O0")
|
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
|
# 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
|
# This ensures the test executable links against the same libgcov version used during compilation
|
||||||
|
|||||||
Reference in New Issue
Block a user