Integrate Launchable into make btest
This commit is contained in:
parent
dface4427d
commit
5b78925455
17
.github/actions/launchable/setup/action.yml
vendored
17
.github/actions/launchable/setup/action.yml
vendored
@ -38,6 +38,19 @@ inputs:
|
|||||||
Directory to (re-)checkout source codes. Launchable retrives the commit information
|
Directory to (re-)checkout source codes. Launchable retrives the commit information
|
||||||
from the directory.
|
from the directory.
|
||||||
|
|
||||||
|
launchable-workspace:
|
||||||
|
required: true
|
||||||
|
default: ${{ github.event.repository.name }}
|
||||||
|
description: >-
|
||||||
|
A workspace name in Launchable
|
||||||
|
|
||||||
|
test-task:
|
||||||
|
required: true
|
||||||
|
default: ${{ matrix.test_task }}
|
||||||
|
description: >-
|
||||||
|
A test task that determine which tests are executed.
|
||||||
|
This value is used in the Launchable flavor.
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
|
|
||||||
@ -77,7 +90,7 @@ runs:
|
|||||||
: # The following envs are necessary in Launchable tokenless authentication.
|
: # The following envs are necessary in Launchable tokenless authentication.
|
||||||
: # https://github.com/launchableinc/cli/blob/v1.80.1/launchable/utils/authentication.py#L20
|
: # https://github.com/launchableinc/cli/blob/v1.80.1/launchable/utils/authentication.py#L20
|
||||||
echo "LAUNCHABLE_ORGANIZATION=${{ github.repository_owner }}" >> $GITHUB_ENV
|
echo "LAUNCHABLE_ORGANIZATION=${{ github.repository_owner }}" >> $GITHUB_ENV
|
||||||
echo "LAUNCHABLE_WORKSPACE=${{ github.event.repository.name }}" >> $GITHUB_ENV
|
echo "LAUNCHABLE_WORKSPACE=${{ inputs.launchable-workspace }}" >> $GITHUB_ENV
|
||||||
: # https://github.com/launchableinc/cli/blob/v1.80.1/launchable/utils/authentication.py#L71
|
: # https://github.com/launchableinc/cli/blob/v1.80.1/launchable/utils/authentication.py#L71
|
||||||
echo "GITHUB_PR_HEAD_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_ENV
|
echo "GITHUB_PR_HEAD_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_ENV
|
||||||
echo "LAUNCHABLE_TOKEN=${{ inputs.launchable-token }}" >> $GITHUB_ENV
|
echo "LAUNCHABLE_TOKEN=${{ inputs.launchable-token }}" >> $GITHUB_ENV
|
||||||
@ -135,7 +148,7 @@ runs:
|
|||||||
working-directory: ${{ inputs.srcdir }}
|
working-directory: ${{ inputs.srcdir }}
|
||||||
post: |
|
post: |
|
||||||
: # record
|
: # record
|
||||||
launchable record tests --flavor os=${{ inputs.os }} --flavor test_task=${{ matrix.test_task }} --flavor test_opts=${test_opts} raw ${report_path}
|
launchable record tests --flavor os=${{ inputs.os }} --flavor test_task=${{ inputs.test-task }} --flavor test_opts=${test_opts} raw ${report_path}
|
||||||
rm -f ${report_path}
|
rm -f ${report_path}
|
||||||
if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }}
|
if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }}
|
||||||
env:
|
env:
|
||||||
|
18
.github/workflows/rjit.yml
vendored
18
.github/workflows/rjit.yml
vendored
@ -67,6 +67,8 @@ jobs:
|
|||||||
srcdir: src
|
srcdir: src
|
||||||
builddir: build
|
builddir: build
|
||||||
makeup: true
|
makeup: true
|
||||||
|
# Set fetch-depth: 10 so that Launchable can receive commits information.
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: Run configure
|
- name: Run configure
|
||||||
env:
|
env:
|
||||||
@ -77,13 +79,27 @@ jobs:
|
|||||||
|
|
||||||
- run: $SETARCH make
|
- run: $SETARCH make
|
||||||
|
|
||||||
|
- name: Set up Launchable
|
||||||
|
uses: ./.github/actions/launchable/setup
|
||||||
|
with:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
test-task: test
|
||||||
|
launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }}
|
||||||
|
builddir: build
|
||||||
|
srcdir: src
|
||||||
|
launchable-workspace: ruby-make-btest
|
||||||
|
test-opts: ${{ matrix.run_opts }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: make test
|
- name: make test
|
||||||
run: |
|
run: |
|
||||||
$SETARCH make -s test RUN_OPTS="$RUN_OPTS"
|
$SETARCH make -s test RUN_OPTS="$RUN_OPTS"
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
env:
|
env:
|
||||||
GNUMAKEFLAGS: ''
|
GNUMAKEFLAGS: ''
|
||||||
RUBY_TESTOPTS: '--tty=no'
|
RUBY_TESTOPTS: >-
|
||||||
|
${{ env.TESTS }}
|
||||||
|
--tty=no
|
||||||
RUN_OPTS: ${{ matrix.run_opts }}
|
RUN_OPTS: ${{ matrix.run_opts }}
|
||||||
|
|
||||||
- name: make test-all
|
- name: make test-all
|
||||||
|
@ -78,6 +78,7 @@ bt = Struct.new(:ruby,
|
|||||||
:platform,
|
:platform,
|
||||||
:timeout,
|
:timeout,
|
||||||
:timeout_scale,
|
:timeout_scale,
|
||||||
|
:launchable_test_reports
|
||||||
)
|
)
|
||||||
BT = Class.new(bt) do
|
BT = Class.new(bt) do
|
||||||
def indent=(n)
|
def indent=(n)
|
||||||
@ -229,6 +230,19 @@ End
|
|||||||
exit true
|
exit true
|
||||||
when /\A-j/
|
when /\A-j/
|
||||||
true
|
true
|
||||||
|
when /--launchable-test-reports=(.*)/
|
||||||
|
if File.exist?($1)
|
||||||
|
# To protect files from overwritten, do nothing when the file exists.
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
require_relative '../tool/lib/test/unit/launchable'
|
||||||
|
BT.launchable_test_reports = writer = Launchable::JsonStreamWriter.new($1)
|
||||||
|
writer.write_array('testCases')
|
||||||
|
at_exit {
|
||||||
|
writer.close
|
||||||
|
}
|
||||||
|
true
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
@ -345,6 +359,45 @@ def concurrent_exec_test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Launchable
|
||||||
|
def show_progress(message = '')
|
||||||
|
faildesc, t = super
|
||||||
|
|
||||||
|
if writer = BT.launchable_test_reports
|
||||||
|
if !faildesc
|
||||||
|
status = 'TEST_PASSED'
|
||||||
|
else
|
||||||
|
status = 'TEST_FAILED'
|
||||||
|
end
|
||||||
|
repo_path = File.expand_path("#{__dir__}/../")
|
||||||
|
relative_path = self.path.delete_prefix("#{repo_path}/")
|
||||||
|
# The test path is a URL-encoded representation.
|
||||||
|
# https://github.com/launchableinc/cli/blob/v1.81.0/launchable/testpath.py#L18
|
||||||
|
test_path = {file: relative_path, testcase: self.id}.map{|key, val|
|
||||||
|
"#{encode_test_path_component(key)}=#{encode_test_path_component(val)}"
|
||||||
|
}.join('#')
|
||||||
|
writer.write_object(
|
||||||
|
{
|
||||||
|
testPath: test_path,
|
||||||
|
status: status,
|
||||||
|
duration: t,
|
||||||
|
createdAt: Time.now.to_s,
|
||||||
|
stderr: faildesc,
|
||||||
|
stdout: nil,
|
||||||
|
data: {
|
||||||
|
lineNumber: self.lineno
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def encode_test_path_component component
|
||||||
|
component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def exec_test(paths)
|
def exec_test(paths)
|
||||||
# setup
|
# setup
|
||||||
load_test paths
|
load_test paths
|
||||||
@ -421,6 +474,7 @@ def target_platform
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Assertion < Struct.new(:src, :path, :lineno, :proc)
|
class Assertion < Struct.new(:src, :path, :lineno, :proc)
|
||||||
|
prepend Launchable
|
||||||
@count = 0
|
@count = 0
|
||||||
@all = Hash.new{|h, k| h[k] = []}
|
@all = Hash.new{|h, k| h[k] = []}
|
||||||
@errbuf = []
|
@errbuf = []
|
||||||
@ -495,9 +549,9 @@ class Assertion < Struct.new(:src, :path, :lineno, :proc)
|
|||||||
$stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}"
|
$stderr.print "#{BT.progress_bs}#{BT.progress[BT_STATE.count % BT.progress.size]}"
|
||||||
end
|
end
|
||||||
|
|
||||||
t = Time.now if BT.verbose
|
t = Time.now if BT.verbose || BT.launchable_test_reports
|
||||||
faildesc, errout = with_stderr {yield}
|
faildesc, errout = with_stderr {yield}
|
||||||
t = Time.now - t if BT.verbose
|
t = Time.now - t if BT.verbose || BT.launchable_test_reports
|
||||||
|
|
||||||
if !faildesc
|
if !faildesc
|
||||||
# success
|
# success
|
||||||
@ -524,6 +578,8 @@ class Assertion < Struct.new(:src, :path, :lineno, :proc)
|
|||||||
$stderr.printf("%-*s%s", BT.width, path, BT.progress[BT_STATE.count % BT.progress.size])
|
$stderr.printf("%-*s%s", BT.width, path, BT.progress[BT_STATE.count % BT.progress.size])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
[faildesc, t]
|
||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
$stderr.puts "\##{@id} #{path}:#{lineno}"
|
$stderr.puts "\##{@id} #{path}:#{lineno}"
|
||||||
raise
|
raise
|
||||||
|
@ -1450,9 +1450,8 @@ module Test
|
|||||||
def setup_options(opts, options)
|
def setup_options(opts, options)
|
||||||
super
|
super
|
||||||
opts.on_tail '--launchable-test-reports=PATH', String, 'Report test results in Launchable JSON format' do |path|
|
opts.on_tail '--launchable-test-reports=PATH', String, 'Report test results in Launchable JSON format' do |path|
|
||||||
require 'json'
|
require_relative '../test/unit/launchable'
|
||||||
require 'uri'
|
options[:launchable_test_reports] = writer = Launchable::JsonStreamWriter.new(path)
|
||||||
options[:launchable_test_reports] = writer = JsonStreamWriter.new(path)
|
|
||||||
writer.write_array('testCases')
|
writer.write_array('testCases')
|
||||||
main_pid = Process.pid
|
main_pid = Process.pid
|
||||||
at_exit {
|
at_exit {
|
||||||
@ -1469,92 +1468,6 @@ module Test
|
|||||||
component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26')
|
component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
|
||||||
# JsonStreamWriter writes a JSON file using a stream.
|
|
||||||
# By utilizing a stream, we can minimize memory usage, especially for large files.
|
|
||||||
class JsonStreamWriter
|
|
||||||
def initialize(path)
|
|
||||||
@file = File.open(path, "w")
|
|
||||||
@file.write("{")
|
|
||||||
@indent_level = 0
|
|
||||||
@is_first_key_val = true
|
|
||||||
@is_first_obj = true
|
|
||||||
write_new_line
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_object obj
|
|
||||||
if @is_first_obj
|
|
||||||
@is_first_obj = false
|
|
||||||
else
|
|
||||||
write_comma
|
|
||||||
write_new_line
|
|
||||||
end
|
|
||||||
@indent_level += 1
|
|
||||||
@file.write(to_json_str(obj))
|
|
||||||
@indent_level -= 1
|
|
||||||
@is_first_key_val = true
|
|
||||||
# Occasionally, invalid JSON will be created as shown below, especially when `--repeat-count` is specified.
|
|
||||||
# {
|
|
||||||
# "testPath": "file=test%2Ftest_timeout.rb&class=TestTimeout&testcase=test_allows_zero_seconds",
|
|
||||||
# "status": "TEST_PASSED",
|
|
||||||
# "duration": 2.7e-05,
|
|
||||||
# "createdAt": "2024-02-09 12:21:07 +0000",
|
|
||||||
# "stderr": null,
|
|
||||||
# "stdout": null
|
|
||||||
# }: null <- here
|
|
||||||
# },
|
|
||||||
# To prevent this, IO#flush is called here.
|
|
||||||
@file.flush
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_array(key)
|
|
||||||
@indent_level += 1
|
|
||||||
@file.write(to_json_str(key))
|
|
||||||
write_colon
|
|
||||||
@file.write(" ", "[")
|
|
||||||
write_new_line
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
return if @file.closed?
|
|
||||||
close_array
|
|
||||||
@indent_level -= 1
|
|
||||||
write_new_line
|
|
||||||
@file.write("}", "\n")
|
|
||||||
@file.flush
|
|
||||||
@file.close
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def to_json_str(obj)
|
|
||||||
json = JSON.pretty_generate(obj)
|
|
||||||
json.gsub(/^/, ' ' * (2 * @indent_level))
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_indent
|
|
||||||
@file.write(" " * 2 * @indent_level)
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_new_line
|
|
||||||
@file.write("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_comma
|
|
||||||
@file.write(',')
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_colon
|
|
||||||
@file.write(":")
|
|
||||||
end
|
|
||||||
|
|
||||||
def close_array
|
|
||||||
write_new_line
|
|
||||||
write_indent
|
|
||||||
@file.write("]")
|
|
||||||
@indent_level -= 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class Runner # :nodoc: all
|
class Runner # :nodoc: all
|
||||||
|
91
tool/lib/test/unit/launchable.rb
Normal file
91
tool/lib/test/unit/launchable.rb
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
require 'json'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
module Launchable
|
||||||
|
##
|
||||||
|
# JsonStreamWriter writes a JSON file using a stream.
|
||||||
|
# By utilizing a stream, we can minimize memory usage, especially for large files.
|
||||||
|
class JsonStreamWriter
|
||||||
|
def initialize(path)
|
||||||
|
@file = File.open(path, "w")
|
||||||
|
@file.write("{")
|
||||||
|
@indent_level = 0
|
||||||
|
@is_first_key_val = true
|
||||||
|
@is_first_obj = true
|
||||||
|
write_new_line
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_object obj
|
||||||
|
if @is_first_obj
|
||||||
|
@is_first_obj = false
|
||||||
|
else
|
||||||
|
write_comma
|
||||||
|
write_new_line
|
||||||
|
end
|
||||||
|
@indent_level += 1
|
||||||
|
@file.write(to_json_str(obj))
|
||||||
|
@indent_level -= 1
|
||||||
|
@is_first_key_val = true
|
||||||
|
# Occasionally, invalid JSON will be created as shown below, especially when `--repeat-count` is specified.
|
||||||
|
# {
|
||||||
|
# "testPath": "file=test%2Ftest_timeout.rb&class=TestTimeout&testcase=test_allows_zero_seconds",
|
||||||
|
# "status": "TEST_PASSED",
|
||||||
|
# "duration": 2.7e-05,
|
||||||
|
# "createdAt": "2024-02-09 12:21:07 +0000",
|
||||||
|
# "stderr": null,
|
||||||
|
# "stdout": null
|
||||||
|
# }: null <- here
|
||||||
|
# },
|
||||||
|
# To prevent this, IO#flush is called here.
|
||||||
|
@file.flush
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_array(key)
|
||||||
|
@indent_level += 1
|
||||||
|
@file.write(to_json_str(key))
|
||||||
|
write_colon
|
||||||
|
@file.write(" ", "[")
|
||||||
|
write_new_line
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
return if @file.closed?
|
||||||
|
close_array
|
||||||
|
@indent_level -= 1
|
||||||
|
write_new_line
|
||||||
|
@file.write("}", "\n")
|
||||||
|
@file.flush
|
||||||
|
@file.close
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def to_json_str(obj)
|
||||||
|
json = JSON.pretty_generate(obj)
|
||||||
|
json.gsub(/^/, ' ' * (2 * @indent_level))
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_indent
|
||||||
|
@file.write(" " * 2 * @indent_level)
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_new_line
|
||||||
|
@file.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_comma
|
||||||
|
@file.write(',')
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_colon
|
||||||
|
@file.write(":")
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_array
|
||||||
|
write_new_line
|
||||||
|
write_indent
|
||||||
|
@file.write("]")
|
||||||
|
@indent_level -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -2,11 +2,12 @@
|
|||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require_relative '../../lib/test/unit/launchable'
|
||||||
|
|
||||||
class TestLaunchable < Test::Unit::TestCase
|
class TestLaunchable < Test::Unit::TestCase
|
||||||
def test_json_stream_writer
|
def test_json_stream_writer
|
||||||
Tempfile.create(['launchable-test-', '.json']) do |f|
|
Tempfile.create(['launchable-test-', '.json']) do |f|
|
||||||
json_stream_writer = Test::Unit::LaunchableOption::JsonStreamWriter.new(f.path)
|
json_stream_writer = Launchable::JsonStreamWriter.new(f.path)
|
||||||
json_stream_writer.write_array('testCases')
|
json_stream_writer.write_array('testCases')
|
||||||
json_stream_writer.write_object(
|
json_stream_writer.write_object(
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user