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
|
||||
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:
|
||||
using: composite
|
||||
|
||||
@ -77,7 +90,7 @@ runs:
|
||||
: # The following envs are necessary in Launchable tokenless authentication.
|
||||
: # https://github.com/launchableinc/cli/blob/v1.80.1/launchable/utils/authentication.py#L20
|
||||
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
|
||||
echo "GITHUB_PR_HEAD_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_ENV
|
||||
echo "LAUNCHABLE_TOKEN=${{ inputs.launchable-token }}" >> $GITHUB_ENV
|
||||
@ -135,7 +148,7 @@ runs:
|
||||
working-directory: ${{ inputs.srcdir }}
|
||||
post: |
|
||||
: # 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}
|
||||
if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }}
|
||||
env:
|
||||
|
18
.github/workflows/rjit.yml
vendored
18
.github/workflows/rjit.yml
vendored
@ -67,6 +67,8 @@ jobs:
|
||||
srcdir: src
|
||||
builddir: build
|
||||
makeup: true
|
||||
# Set fetch-depth: 10 so that Launchable can receive commits information.
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Run configure
|
||||
env:
|
||||
@ -77,13 +79,27 @@ jobs:
|
||||
|
||||
- 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
|
||||
run: |
|
||||
$SETARCH make -s test RUN_OPTS="$RUN_OPTS"
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
GNUMAKEFLAGS: ''
|
||||
RUBY_TESTOPTS: '--tty=no'
|
||||
RUBY_TESTOPTS: >-
|
||||
${{ env.TESTS }}
|
||||
--tty=no
|
||||
RUN_OPTS: ${{ matrix.run_opts }}
|
||||
|
||||
- name: make test-all
|
||||
|
@ -78,6 +78,7 @@ bt = Struct.new(:ruby,
|
||||
:platform,
|
||||
:timeout,
|
||||
:timeout_scale,
|
||||
:launchable_test_reports
|
||||
)
|
||||
BT = Class.new(bt) do
|
||||
def indent=(n)
|
||||
@ -229,6 +230,19 @@ End
|
||||
exit true
|
||||
when /\A-j/
|
||||
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
|
||||
false
|
||||
end
|
||||
@ -345,6 +359,45 @@ def concurrent_exec_test
|
||||
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)
|
||||
# setup
|
||||
load_test paths
|
||||
@ -421,6 +474,7 @@ def target_platform
|
||||
end
|
||||
|
||||
class Assertion < Struct.new(:src, :path, :lineno, :proc)
|
||||
prepend Launchable
|
||||
@count = 0
|
||||
@all = Hash.new{|h, k| h[k] = []}
|
||||
@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]}"
|
||||
end
|
||||
|
||||
t = Time.now if BT.verbose
|
||||
t = Time.now if BT.verbose || BT.launchable_test_reports
|
||||
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
|
||||
# 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])
|
||||
end
|
||||
end
|
||||
|
||||
[faildesc, t]
|
||||
rescue Interrupt
|
||||
$stderr.puts "\##{@id} #{path}:#{lineno}"
|
||||
raise
|
||||
|
@ -1450,9 +1450,8 @@ module Test
|
||||
def setup_options(opts, options)
|
||||
super
|
||||
opts.on_tail '--launchable-test-reports=PATH', String, 'Report test results in Launchable JSON format' do |path|
|
||||
require 'json'
|
||||
require 'uri'
|
||||
options[:launchable_test_reports] = writer = JsonStreamWriter.new(path)
|
||||
require_relative '../test/unit/launchable'
|
||||
options[:launchable_test_reports] = writer = Launchable::JsonStreamWriter.new(path)
|
||||
writer.write_array('testCases')
|
||||
main_pid = Process.pid
|
||||
at_exit {
|
||||
@ -1469,92 +1468,6 @@ module Test
|
||||
component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26')
|
||||
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
|
||||
|
||||
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 'tempfile'
|
||||
require 'json'
|
||||
require_relative '../../lib/test/unit/launchable'
|
||||
|
||||
class TestLaunchable < Test::Unit::TestCase
|
||||
def test_json_stream_writer
|
||||
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_object(
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user