Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci-homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +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@a46850981292c4bbc0c41715f286ad95adaaaf4a # v2026.625.20301
with:
actionlint_config: "---\n# empty config"
formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
Expand All @@ -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

Expand Down
13 changes: 8 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,16 @@ jobs:
coverage: true
pr: true
- name: Homebrew-macos-14
coverage: false
coverage: true
coverage_file: coverage.lcov
pr: true
- name: Homebrew-macos-15
coverage: false
coverage: true
coverage_file: coverage.lcov
pr: true
- name: Homebrew-macos-26
coverage: false
coverage: true
coverage_file: coverage.lcov
pr: true
- name: Homebrew-ubuntu-24.04
coverage: true
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -343,7 +346,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
Expand Down
208 changes: 175 additions & 33 deletions packaging/sunshine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@

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
TEST_BINARY = "test_sunshine".freeze
IS_UPSTREAM_REPO = ENV.fetch("GITHUB_REPOSITORY", "") == "LizardByte/Sunshine"

desc "@PROJECT_DESCRIPTION@"
Expand Down Expand Up @@ -48,20 +53,16 @@
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"
depends_on "miniupnpc"
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 "gcovr" => [:build, :test]
depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build if build.with? "cuda"
depends_on "python3" => :build
depends_on "at-spi2-core"
Expand Down Expand Up @@ -183,6 +184,7 @@
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"
Expand Down Expand Up @@ -248,6 +250,164 @@
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"

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[LLVM_PROFILE_FILE_ENV]

Check failure on line 285 in packaging/sunshine.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide a default value or use ENV.fetch() to validate this environment variable access.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZ78zyao1YE0NPfY-HNZ&open=AZ78zyao1YE0NPfY-HNZ&pullRequest=5331
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
args = base_cmake_args
add_test_args(args)
Expand All @@ -267,7 +427,7 @@
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?
Expand All @@ -279,6 +439,7 @@
setup_build_environment
build_and_install_project
install_platform_specific_files
collect_test_artifacts
end

service do
Expand Down Expand Up @@ -314,33 +475,14 @@
# 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 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", "")
end
end
end
Expand Down
10 changes: 8 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading