Automatically extract package dependencies from project definition file
...rather than depend on manually pre-extracted and commited dependencies.
This makes the code more robust as well easier to use. It will also allow
for parallelized builds (though some changes need to be done to `stc`)
Big thanks to Patrik Svestka for providing me a Ruby code to extract
dependencies.
Yay, I wanted for this for so long...
TESTREPORT_CLASS = 'Builder::TestReport'
LINTREPORT_CLASS = 'Builder::LintReport'
# A helper class to keep a summary of a test report
class TestReportSummary
SUMMARIES = []
PATTERN = /tests=\"(?<run>\d+)\".+failures=\"(?<failed>\d+)\" errors=\"(?<errors>\d+)\" skipped=\"(?<skipped>\d+)\"/
attr_reader :pkg, :run, :failed, :errors, :skipped
def passed
return @run - @failed - @errors - @skipped
end
def outcome
if @failed > 0 or @errors > 0 then
return 'FAILED'
else
return 'PASSED'
end
end
# Creates a new summary for given package and report file.
def initialize(pkg_name, report_file)
if not File.exist? report_file then
raise Exception.new("Report file does not exist! #{report_file}");
end
@pkg = pkg_name
matches = PATTERN.match(IO.read(report_file, 512))
if not matches then
raise Exception.new("Cannot \"parse\" report file!")
# Maybe the buffer is too small? Try to read up more
# data....
end
@run = matches['run'].to_i
@failed = matches['failed'].to_i
@errors = matches['errors'].to_i
@skipped = matches['skipped'].to_i
end
end
desc "Run tests"
task :'test' => :'test:all'
desc "Run tests (alias for target test)"
task :'tests' => :'test'
task :'setup:tasks' => :'setup:tasks:test'
def run_report(app, packages, report, global_opts = '', report_opts = '')
#run_report_st = BUILD_DIR / 'stx' / 'goodies' / 'builder' / 'reports' / 'report-runner-old.st'
run_report_st = BUILD_DIR / 'stx' / 'goodies' / 'builder' / 'reports' / 'report-runner.st'
coveragerportformat_dot_st = BUILD_DIR / 'stx' / 'goodies' / 'builder' / 'reports' / 'Builder__CoverageReportFormat.st'
report_dir = File.expand_path(REPORT_DIR)
tmp_dir = File.expand_path(TMP_DIR)
# Set STX_TMPDIR environment to make sure all temporary files created by
# Smalltalk/X goes to a local tmp directory (which should be discarded
# regularly). This helps to avoid garbage to heap up on Windows slaves
# assuming workspaces is thrown away often.
ENV['STX_TMPDIR'] = tmp_dir
if app
exe_dir = BUILD_DIR / app.directory
if win32?
exe = "#{app.executable}.com"
else
exe = "./#{app.executable}"
end
else
exe_dir = BUILD_DIR / 'stx' / 'projects' / 'smalltalk'
if win32?
exe = "stx.com"
else
exe = "./stx"
end
end
if not File.directory? tmp_dir then
mkdir_p tmp_dir
end
chdir exe_dir do
packages_args = ''
packages.each { | p | packages_args += " -p #{p}" }
if File.exist?(coveragerportformat_dot_st)
runner_opts = "-abortOnSEGV -I --execute #{run_report_st}"
else
runner_opts = "-I -f #{run_report_st}"
end
sh "#{exe} #{runner_opts} #{global_opts} -i \"#{BUILD_ID}\" -D \"#{report_dir}\" -r #{report} #{report_opts} #{packages_args}"
end
rm_rf tmp_dir
end
task :'setup:tasks:test' do
$__testresults__ = []
app = project.application
project.packages.each do | pkg |
if pkg.test
task "test:package:#{pkg.name}" => [ 'stx:goodies/builder/reports', REPORT_DIR ] do
if pkg.coverage
run_report(app, [ pkg.name ], TESTREPORT_CLASS, '', '--coverage')
else
run_report(app, [ pkg.name ], TESTREPORT_CLASS)
end
# Extract summary from XML report and keep it. Yeah, parsing XML
# using regexps is a bad thing, but it's quick and lot less code!
report_file = File.expand_path(REPORT_DIR) / "#{pkg.name_components.join('_')}-#{BUILD_ID}-Test.xml"
report_summary = TestReportSummary.new(pkg.name, report_file);
TestReportSummary::SUMMARIES << report_summary
end
task :'test:packages' => "test:package:#{pkg.name}"
end
if pkg.lint
task "lint:package:#{pkg.name}" => [ 'stx:goodies/builder/reports', REPORT_DIR ] do
#puts "LINT DISABLED (because of some bug in recent SmallLint - runs out of memory)"
run_report(app, [ pkg.name ], LINTREPORT_CLASS)
end
task :'lint:packages' => "lint:package:#{pkg.name}"
end
end
end
task :'setup:tasks' => :'setup:tasks:test'
task :'test:setup' => :'setup'
task :'lint:setup' => :'setup'
namespace :'test' do
task :'all' => [ :'setup', :'pre', :'main', :'post' ]
task :'pre'
task :'post'
directory REPORT_DIR
task :'setup' => :'setup:dependencies'
task :'main' => [:'setup', :'packages', :'summary' ]
task :'packages'
task :'summary' do
outcome = 'PASSED'
puts
puts "OVERALL SUMMARY"
puts
TestReportSummary::SUMMARIES.each do | test_summary |
puts "%-20s %s - %d run, %d passed, %d skipped, %d failed, %d errors" % [
test_summary.pkg,
test_summary.outcome,
test_summary.run,
test_summary.passed,
test_summary.skipped,
test_summary.failed,
test_summary.errors
]
if test_summary.failed > 0 or test_summary.errors > 0 then
outcome = 'FAILED'
end
end
puts
puts outcome
end
end
desc "Run static analysis on the code (SmallLint)"
task :'lint' => :'lint:all'
namespace :'lint' do
task :'all' => [ :'setup', :'pre', :'main', :'post' ]
task :'pre'
task :'post'
directory REPORT_DIR
task :'setup'
task :'main' => [:'setup', :'packages' ]
task :'packages'
end