lib/ttnt/test_selector.rb
require 'set'
require 'rugged'
require 'ttnt/metadata'
require 'ttnt/test_to_code_mapping'
module TTNT
# Select tests using Git information and {TestToCodeMapping}.
class TestSelector
attr_reader :tests
# @param repo [Rugged::Reposiotry] repository of the project
# @param target_sha [String] sha of the target object
# (nil means to target current working tree)
# @param test_files [#include?] candidate test files
def initialize(repo, target_sha, test_files)
@repo = repo
storage_src_sha = target_sha ? target_sha : @repo.head.target_id
@metadata = MetaData.new(repo, storage_src_sha)
@target_obj = @repo.lookup(target_sha) if target_sha
# Base should be the commit `ttnt:anchor` has run on.
# NOT the one test-to-code mapping was commited to.
@base_obj = find_anchored_commit
@test_files = test_files
end
# Select tests using differences in anchored commit and target commit
# (or current working tree) and {TestToCodeMapping}.
#
# @return [Set] a set of tests that might be affected by changes in base_sha...target_sha
def select_tests!
# select all tests if anchored commit does not exist
return Set.new(@test_files) unless @base_obj
@tests ||= Set.new
opts = {
include_untracked: true,
recurse_untracked_dirs: true
}
diff = defined?(@target_obj) ? @base_obj.diff(@target_obj, opts) : @base_obj.diff_workdir(opts)
diff.each_patch do |patch|
file = patch.delta.old_file[:path]
if test_file?(file)
@tests << file
else
select_tests_from_patch(patch)
end
end
@tests.delete(nil)
end
private
def mapping
@mapping ||= begin
sha = defined?(@target_obj) ? @target_obj.oid : @repo.head.target_id
TTNT::TestToCodeMapping.new(@repo, sha)
end
end
# Select tests which are affected by the change of given patch.
#
# @param patch [Rugged::Patch]
# @return [Set] set of selected tests
def select_tests_from_patch(patch)
target_lines = Set.new
file = patch.delta.old_file[:path]
prev_line = nil
patch.each_hunk do |hunk|
hunk.each_line do |line|
case line.line_origin
when :addition
if prev_line && !prev_line.addition?
target_lines << prev_line.old_lineno
elsif prev_line.nil?
target_lines << hunk.old_start
end
when :deletion
target_lines << line.old_lineno
end
prev_line = line
end
end
target_lines.each do |line|
@tests += mapping.get_tests(file: file, lineno: line)
end
end
# Find the commit `ttnt:anchor` has been run on.
def find_anchored_commit
if @metadata['anchored_commit']
@repo.lookup(@metadata['anchored_commit'])
else
nil
end
end
# Check if the given file is a test file.
#
# @param filename [String]
def test_file?(filename)
@test_files.include?(filename)
end
end
end