Skip to content

Commit

Permalink
Merge pull request #18 from oxidize-rb/add-test-scripts
Browse files Browse the repository at this point in the history
Add GH Action to test cross-compilation
  • Loading branch information
gjtorikian authored Mar 28, 2023
2 parents 2e55ffa + 7195e43 commit e193b4e
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 0 deletions.
37 changes: 37 additions & 0 deletions test-gem-build/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: "Test gem"
description: "A GitHub action to test the cross-compilation of a Ruby gem built on multiple platforms."
author: "@gjtorikian"
branding:
icon: "download-cloud"
color: "gray-dark"
inputs:
platform:
description: "The platform which the gem was cross-compiled for (e.g. `x86_64-linux`)"
required: true
ruby-versions:
description: "The Ruby versions the gem was cross-compiled for (e.g. `2.7,3.0,3.1`)"
default: "default"
runs:
using: "composite"
steps:
- name: Run tests
id: run-tests
shell: bash
env:
INPUT_PLATFORM: "${{ inputs.platform }}"
INPUT_RUBY_VERSIONS: "${{ inputs.ruby-versions }}"
run: |
: Run tests
set -x
args=()
args+=("--platform")
args+=("$INPUT_PLATFORM")
if [ "$INPUT_RUBY_VERSIONS" != "default" ]; then
args+=("--ruby-versions")
args+=("$INPUT_RUBY_VERSIONS")
fi
echo $(ruby $GITHUB_ACTION_PATH/test.rb ${args[@]})
64 changes: 64 additions & 0 deletions test-gem-build/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# ⚡️ `oxidize-rb/test-gem-build`

A GitHub action to test whether your Ruby on Rust gem built successfully.

## Example usage

```yaml
---
name: CI

on:
push:
tags:
- "v*"

jobs:
ci-data:
runs-on: ubuntu-latest
outputs:
result: ${{ steps.fetch.outputs.result }}
steps:
- uses: oxidize-rb/actions/fetch-ci-data@v1
id: fetch
with:
supported-ruby-platforms: |
exclude: [arm-linux]
stable-ruby-versions: |
exclude: [head]
cross-gem:
name: Compile native gem for ${{ matrix.platform }}
runs-on: ubuntu-latest
needs: ci-data
strategy:
matrix:
platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }}
steps:
- uses: actions/checkout@v2

- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.1"

- uses: oxidize-rb/actions/cross-gem@v1
id: cross-gem
with:
platform: ${{ matrix.ruby-platform }}
ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }}

- uses: oxidize-rb/actions/test-gem-build@main
with:
platform: ${{ matrix.ruby-platform }}
ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }}
```
## Inputs
<!-- inputs -->
| Name | Description | Default |
| ----------------- | ----------------------------------------------------------------------- | --------- |
| **platform** | The platform which the gem was cross-compiled for (e.g. `x86_64-linux`) | |
| **ruby-versions** | The Ruby versions the gem was cross-compiled for (e.g. `2.7,3.0,3.1`) | `default` |

<!-- /inputs -->
156 changes: 156 additions & 0 deletions test-gem-build/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# frozen_string_literal: true
#
# this script inspects the contents of a cross-compiled gem file -- both the files and the gemspec -- to ensure
# we're packaging what we expect, and that we're not packaging anything we don't expect.
#
require "bundler/inline"
require "net/http"
require "json"

gemfile do
source "https://rubygems.org"
gem "ansi"
gem "builder"
gem "minitest", "~> 5.0"
gem "minitest-reporters", "~> 1.6"
gem "ruby-progressbar"
end

options = {}
OptionParser.new do |opts|
opts.on("-p", "--platform PLATFORM", "Platform to build for (i.e. x86_64-linux)") do |platform|
options[:platform] = platform
end

opts.on("-r", "--ruby-versions LIST", "List all supported Ruby versions") do |arg|
vers = arg.split(/[^0-9.]/).map do |v|
parts = v.split(".")
parts.join(".")
end

options[:ruby_versions] = vers.join(":")
end
end.parse!

require "yaml"

def usage_and_exit(message = nil)
puts "ERROR: #{message}" if message
puts "USAGE: #{File.basename(__FILE__)} -p $PLATFORM [options]"
exit(1)
end

usage_and_exit if ARGV.include?("-h")
usage_and_exit if options[:platform].nil?

gemfile = "pkg/*-#{options[:platform]}.gem"
gemfile = File.expand_path(gemfile)

gemfile_contents = Dir.mktmpdir do |dir|
Dir.chdir(dir) do
unless system("tar -xf #{gemfile} data.tar.gz")
raise "could not unpack gem #{gemfile}"
end

%x(tar -ztf data.tar.gz).split("\n")
end
end

gemspec = Dir.mktmpdir do |dir|
Dir.chdir(dir) do
unless system("tar -xf #{gemfile} metadata.gz")
raise "could not unpack gem #{gemfile}"
end

YAML.safe_load(
%x(gunzip -c metadata.gz),
permitted_classes: [Gem::Specification, Gem::Version, Gem::Dependency, Gem::Requirement, Time, Symbol],
)
end
end

puts "---------- gemfile contents ----------"
puts gemfile_contents
puts
puts "---------- gemspec ----------"
puts gemspec.to_ruby
puts

require "minitest/autorun"
require "minitest/reporters"

Minitest::Reporters.use!([Minitest::Reporters::SpecReporter.new])

def fetch_json(url)
uri = URI(url)
response = Net::HTTP.get(uri)
JSON.parse(response)
end

describe File.basename(gemfile) do
let(:all_supported_ruby_versions) do
fetch_json("https://cache.ruby-lang.org/pub/misc/ci_versions/cruby.json").delete_if { |v| v == "head" }
end

let(:actual_supported_ruby_versions) do
ruby_versions = options[:ruby_versions] || all_supported_ruby_versions
return all_supported_ruby_versions if ruby_versions.nil?
ruby_versions.split(":").uniq.delete_if { |v| v == "head" }.map { |ver| ver.split(".").take(2).join(".") }.sort
end

describe "setup" do
it "gemfile contains some files" do
actual = gemfile_contents.length
assert_operator(actual, :>, 0, "expected gemfile to contain more than 0 files")
end

it "gemspec is a Gem::Specification" do
assert_equal(Gem::Specification, gemspec.class)
end
end

describe "native platform" do
it "does not depend on rb-sys" do
refute(gemspec.dependencies.find { |d| d.name == "rb-sys" })
end

it "contains expected shared library files" do
actual_supported_ruby_versions.each do |version|
actual = gemspec.lib_files.find do |file|
File.fnmatch?("lib/*/#{version}/*.{so,bundle}", file, File::FNM_EXTGLOB)
end
assert(actual, "expected to find shared library file for ruby #{version} in lib/#{version}")
end

actual = gemspec.lib_files.find do |file|
File.fnmatch?("lib/?/*.{so,bundle}", file, File::FNM_EXTGLOB)
end
refute(actual, "did not expect to find shared library file in lib/")

actual = gemspec.lib_files.find_all do |file|
File.fnmatch?("lib/*/*/*.{so,bundle}", file, File::FNM_EXTGLOB)
end
assert_equal(
actual_supported_ruby_versions.length,
actual.length,
"did not expect extra shared library files",
)
end

it "sets required_ruby_version appropriately" do
unsupported_versions = all_supported_ruby_versions - actual_supported_ruby_versions
actual_supported_ruby_versions.each do |v|
assert(
gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
"required_ruby_version='#{gemspec.required_ruby_version}' should support ruby #{v}",
)
end
unsupported_versions.each do |v|
refute(
gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
"required_ruby_version='#{gemspec.required_ruby_version}' should not support ruby #{v}",
)
end
end
end
end

0 comments on commit e193b4e

Please sign in to comment.