Integrate Launchable into make btest

This commit is contained in:
Naoto Ono 2024-07-06 18:20:41 +09:00 committed by Hiroshi SHIBATA
parent dface4427d
commit 5b78925455
6 changed files with 185 additions and 95 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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(
{