All Files (99.53% covered at 13.82 hits/line)
63 files in total.
1069 relevant lines.
1064 lines covered and
5 lines missed
# frozen_string_literal: true
- 1
require 'dragnet/version'
- 1
require_relative 'dragnet/cli'
- 1
require_relative 'dragnet/errors'
- 1
require_relative 'dragnet/explorer'
- 1
require_relative 'dragnet/exporter'
- 1
require_relative 'dragnet/exporters'
- 1
require_relative 'dragnet/multi_repository'
- 1
require_relative 'dragnet/repo'
- 1
require_relative 'dragnet/validator'
- 1
require_relative 'dragnet/validators'
- 1
require_relative 'dragnet/verifiers'
# Main namespace for the gem
- 1
module Dragnet
end
# frozen_string_literal: true
- 1
require_relative 'errors/incompatible_repository_error'
- 1
module Dragnet
# Base class for Dragnet's repository classes.
- 1
class BaseRepository
- 1
attr_reader :path
# @param [Pathname] path The path were the repository is located.
- 1
def initialize(path:)
- 72
@path = path
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def git
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def branch
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def branches
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def diff
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def head
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def remote_uri_path
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def repositories
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def branches_with(_commit)
- 1
incompatible_repository(__method__)
end
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
- 1
def branches_with_head
- 1
incompatible_repository(__method__)
end
- 1
private
# @param [String] message The message for the raised error.
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
# with the given message.
- 1
def incompatible_repository(message)
- 9
raise Dragnet::Errors::IncompatibleRepositoryError, message
end
end
end
# frozen_string_literal: true
- 1
require_relative 'cli/master.rb'
- 1
module Dragnet
# Namespace for the Gem's CLI
- 1
module CLI; end
end
# frozen_string_literal: true
- 1
require 'active_support'
- 1
require 'active_support/core_ext/hash'
- 1
require 'colorize'
- 1
require 'thor'
- 1
require 'yaml'
- 1
require_relative 'logger'
- 1
module Dragnet
- 1
module CLI
# Base class for all CLI classes.
- 1
class Base < Thor
- 1
include Thor::Actions
# Exit status codes
- 1
E_CONFIG_LOAD_ERROR = 1
- 1
attr_reader :configuration, :logger
- 1
class_option :configuration, aliases: :c, desc: 'Configuration file',
default: '.dragnet.yaml', required: true
- 1
class_option :quiet, aliases: :q, default: false, type: :boolean,
desc: 'Suppresses all terminal output (except for critical errors)'
# Tells Thor to return an unsuccessful return code (different from 0) if
# an error is raised.
- 1
def self.exit_on_failure?
true
end
# Creates a new instance of the class. Called by Thor when a command is
# executed. Creates a logger for the class passing Thor's shell to it
# (Thor's shell handles the output to the console)
- 1
def initialize(*args)
- 55
super
- 55
@logger = Dragnet::CLI::Logger.new(shell)
end
- 1
private
# @return [String] Returns the name of the configuration file (passed via
# the -c command line switch).
- 1
def configuration_file
- 109
@configuration_file ||= options[:configuration]
end
# Loads the configuration from the given configuration file. (This is a
# dumb loader, it basically loads the whole YAML file into a hash, no
# parsing, validation or checking takes place)
- 1
def load_configuration
- 53
logger.info "Loading configuration file #{configuration_file}..."
- 53
@configuration = YAML.safe_load(File.read(configuration_file)).deep_symbolize_keys
rescue StandardError => e
- 3
fatal_error("Unable to load the given configuration file: '#{configuration_file}'", e, E_CONFIG_LOAD_ERROR)
end
# Prints the given message alongside the message of the given exception
# and then terminates the process with the given exit code.
# @param [String] message The error message.
# @param [Exception] exception The exception that caused the fatal error.
# @param [exit_code] exit_code The exit code.
- 1
def fatal_error(message, exception, exit_code)
- 20
puts 'Error: '.colorize(:light_red) + message
- 20
puts " #{exception.message}"
- 20
exit(exit_code)
end
end
end
end
# frozen_string_literal: true
- 1
require 'colorize'
- 1
module Dragnet
- 1
module CLI
# A logger for the CLI. It uses the +say+ method in Thor's +Shell+ class to
# print the messages to the output, honoring the status of the +quiet+
# command line switch.
- 1
class Logger
- 1
attr_reader :shell, :log_level
- 1
LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
- 1
DEFAULT_LOG_LEVEL = :info
- 1
PADDING_STRING = ' '
- 1
PADDING_WIDTH = 7
# Creates a new instance of the class.
# @param [Thor::Shell::Basic] shell A reference to Thor's +Shell+ this
# will be used to send the output to the terminal in which Thor was
# started.
# @param [Symbol] log_level The log level for the logger. The higher the
# level the less output will be printed.
# @see LEVELS
- 1
def initialize(shell, log_level = DEFAULT_LOG_LEVEL)
- 15
raise ArgumentError, "Unknown logger level: #{log_level}" unless LEVELS.keys.include?(log_level)
- 14
@log_level = LEVELS[log_level]
- 14
@shell = shell
end
# Prints a message with log level +debug+
# @param [String] message The message to print
- 1
def debug(message)
- 3
output(:debug, :green, message)
end
# Prints a message with log level +info+
# @param [String] message The message to print
- 1
def info(message)
- 3
output(:info, :blue, message)
end
# Prints a message with log level +warn+
# @param [String] message The message to print
- 1
def warn(message)
- 3
output(:warn, :yellow, message)
end
# Prints a message with log level +error+
# @param [String] message The message to print
- 1
def error(message)
- 2
output(:error, :red, message)
end
- 1
private
# Prints the given message with the given level and text color (only the
# name of the level will be colored).
# @param [Symbol] level The log level
# @param [Symbol] color The color to use. One of the colors available for
# the +#colorize+ method.
# @param [String] message The message to print.
# @see Colorize::InstanceMethods#colorize
- 1
def output(level, color, message)
- 11
return unless log_level <= LEVELS[level]
- 8
shell.say "#{level.to_s.capitalize}:".ljust(PADDING_WIDTH, PADDING_STRING).colorize(color) + message
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../explorer'
- 1
require_relative '../exporter'
- 1
require_relative '../multi_repository'
- 1
require_relative '../repository'
- 1
require_relative '../validator'
- 1
require_relative '../verifier'
- 1
require_relative '../version'
- 1
require_relative 'base'
- 1
module Dragnet
- 1
module CLI
# Entry point class for the Dragnet CLI. Includes all the commands and
# sub-commands of the CLI.
#
# The class should not contain any logic, everything should be delegated to
# helper classes as soon as possible. Only exceptions are error handling and
# message printing.
- 1
class Master < Dragnet::CLI::Base
- 1
E_MISSING_PARAMETER_ERROR = 2
- 1
E_NO_MTR_FILES_FOUND = 3
- 1
E_GIT_ERROR = 4
- 1
E_EXPORT_ERROR = 5
- 1
E_INCOMPATIBLE_REPOSITORY = 6
- 1
E_ERRORS_DETECTED = 16
- 1
E_FAILED_TESTS = 32
- 1
map %w[--version -v] => :version
- 1
desc '--version', 'Prints the current version of the Gem'
- 1
def version
- 2
say "Dragnet #{Dragnet::VERSION}"
- 2
say "Copyright (c) #{Time.now.year} ESR Labs GmbH esrlabs.com"
end
- 1
desc 'check [PATH]', 'Executes the verification procedure. '\
'Loads the given configuration file and executes the verify procedure on the given path '\
'(defaults to the value of the "path" key in the configuration file or the current '\
'working directory if none of them is given)'
- 1
method_option :export,
aliases: 'e', type: :string, repeatable: true,
desc: 'If given, the results of the verification procedure will be exported to'\
' the given file. The format of the export will be deducted from the given'\
" file's name"
- 1
method_option :'multi-repo',
aliases: '-m', type: :boolean, default: false,
desc: 'Enables the multi-repo compatibility mode. This prevents Dragnet from assuming'\
' that [PATH] refers to a Git repository allowing it to run even if that is not the case.'\
" Using this option will cause Dragnet to raise an error if it finds a MTR which doesn't"\
" have a 'repos' attribute"
- 1
def check(path = nil)
- 53
load_configuration
- 50
self.path = path
- 50
files = explore
- 44
test_records, errors = validate(files)
- 44
verify(test_records)
- 39
export(test_records, errors) if options[:export]
- 33
exit_code = 0
- 33
exit_code |= E_ERRORS_DETECTED if errors.any?
- 39
exit_code |= E_FAILED_TESTS unless test_records.all? { |test_record| test_record.verification_result.passed? }
- 33
exit(exit_code) if exit_code.positive? # doing exit(0) will stop RSpec execution.
end
- 1
private
# Runs the explorer on the given path.
# @return [Array<Pathname>] The array of found MTR files.
- 1
def explore
- 50
glob_patterns = configuration[:glob_patterns]
begin
- 50
explorer = Dragnet::Explorer.new(path: path, glob_patterns: glob_patterns, logger: logger)
- 47
explorer.files
- 6
rescue ArgumentError => e
- 3
fatal_error('Initialization error. Missing or malformed parameter.', e, E_MISSING_PARAMETER_ERROR)
rescue Dragnet::Errors::NoMTRFilesFoundError => e
- 3
fatal_error('No MTR Files found.', e, E_NO_MTR_FILES_FOUND)
end
end
# Executes the validator on the given MTR files.
# @param [Array<Pathname>] files The files to run the validator on.
# @return [Array (Array<Dragnet::TestRecord>, Array<Hash>)] An array.
# - The first element is an array of +TestRecord+s with the MTR data.
# One for each valid MTR file.
# - The second element contains the errors occurred during the
# validation process. Can be an empty array.
- 1
def validate(files)
- 44
validator = Dragnet::Validator.new(files: files, path: path, logger: logger)
- 44
[validator.validate, validator.errors]
end
# Executes the verification on the given MTRs
# @param [Array<Dragnet::TestRecord>] test_records The array of MTRs on
# which the verification should be executed.
- 1
def verify(test_records)
- 44
verifier = Dragnet::Verifier.new(test_records: test_records, repository: repository, logger: logger)
- 41
verifier.verify
rescue ArgumentError => e
- 3
fatal_error("Could not open the specified path: #{path} as a Git Repository", e, E_GIT_ERROR)
rescue Dragnet::Errors::IncompatibleRepositoryError => e
- 2
incompatible_repository_error(e)
end
# Executes the export process.
# @param [Array<Dragnet::TestRecord>] test_records The validated and
# verified test records.
# @param [Array<Hashes>] errors The array of Hashes with the MTR files
# that didn't pass the validation process.
- 1
def export(test_records, errors)
- 9
exporter = Dragnet::Exporter.new(
test_records: test_records, errors: errors, repository: repository, targets: options[:export], logger: logger
)
- 9
exporter.export
rescue Dragnet::Errors::UnknownExportFormatError, Dragnet::Errors::UnableToWriteReportError => e
- 4
fatal_error('Export failed', e, E_EXPORT_ERROR)
rescue Dragnet::Errors::IncompatibleRepositoryError => e
- 2
incompatible_repository_error(e)
end
# @return [Pathname] The path of the directory where the verification
# process should be executed.
- 1
def path
- 141
@path || set_fallback_path
end
# @param [Pathname, String] path The path of the directory where the
# verification process should be executed.
- 1
def path=(path)
- 94
@path = path ? Pathname.new(path) : nil
end
# @raise [ArgumentError] If the given path is not a valid git repository.
# @return [Dragnet::Repository, Dragnet::MultiRepository] One of the
# possible Repository objects.
- 1
def repository
- 53
@repository ||= create_repository
end
# Creates the appropriate Repository object in accordance to the status of
# the +multi-repo+ command line option.
# @return [Dragnet::MultiRepository] If +multi-repo+ was set to +true+
# @return [Dragnet::Repository] If +multi_repo+ was set to +false+
- 1
def create_repository
- 44
options[:'multi-repo'] ? Dragnet::MultiRepository.new(path: path) : Dragnet::Repository.new(path: path)
end
# Prints a message and exits with the proper error code when a
# +Dragnet::Errors::IncompatibleRepositoryError+ is raised.
# @param [Dragnet::Errors::IncompatibleRepositoryError] error The raised
# error.
- 1
def incompatible_repository_error(error)
- 4
fatal_error('Incompatible git operation:', error, E_INCOMPATIBLE_REPOSITORY)
end
# Called when no path has been given by the user explicitly. The method
# uses the configured path or the current working directory as a fallback.
# @return [Pathname] The constructed fallback path.
- 1
def set_fallback_path
# The && causes Ruby to return the value of +@path+ AFTER it has been
# assigned (converted to a Pathname)
- 44
(self.path = configuration[:path] || Dir.pwd) && @path
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'errors/not_a_repository_error'
- 1
require_relative 'errors/repo_path_not_found_error'
- 1
module Dragnet
# Namespace to house all the error classes for the gem.
- 1
module Errors; end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Errors
# Base class for all errors raised by the gem.
- 1
class Error < StandardError; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when one of the files referenced by a MTR File
# doesn't exist in the repository.
- 1
class FileNotFoundError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Errors
# An error to be raised when an attempt is made to perform an action on a
# multi-repo set-up which can only be performed on a single-repo set-up.
# For example, trying to perform a +diff+ operation on the multi-repo root.
- 1
class IncompatibleRepositoryError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when an attempt is made to retrieve the runtime
# when one or more timestamp attributes are missing.
- 1
class MissingTimestampAttributeError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when the +Explorer+ is unable to locate MTR files
# with the given glob patterns inside the specified path.
- 1
class NoMTRFilesFoundError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when the path of a repository entry in a MTR with
# multiple repositories doesn't point to an actual git repository.
- 1
class NotARepositoryError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to raise when the +path+ given for a +repos+ entry cannot be found.
- 1
class RepoPathNotFoundError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when Dragnet cannot write to one of the given export
# files.
- 1
class UnableToWriteReportError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when an export target file is given for which the
# format is unknown (cannot be deduced from its extension).
- 1
class UnknownExportFormatError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'error'
- 1
module Dragnet
- 1
module Errors
# An error to be raised when an attempt is made to create an entity with
# invalid data.
- 1
class ValidationError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Errors
# An error to be raised when there is a formatting problem with a YAML file.
# For example a missing key. (This error doesn't cover Syntax Errors)
- 1
class YAMLFormatError < Dragnet::Errors::Error; end
end
end
# frozen_string_literal: true
- 1
require_relative 'errors/no_mtr_files_found_error'
- 1
module Dragnet
# This class searches for Manual Test Record files inside a given path by
# using the given Glob patterns.
- 1
class Explorer
- 1
attr_reader :path, :glob_patterns, :logger
# Creates a new instance of the class.
# @param [Pathname] path The path that should be explored.
# @param [String, Array<String>] glob_patterns The glob pattern or glob
# patterns to use when exploring the specified path.
# @param [#info] logger A logger object to use for output.
# @raise [ArgumentError] If +path+ or +glob_patterns+ are +nil+ or if they
# don't have one of the expected types.
- 1
def initialize(path:, glob_patterns:, logger:)
- 13
validate_path(path)
- 11
validate_patterns(glob_patterns)
- 8
@path = path
- 8
@glob_patterns = *glob_patterns
- 8
@logger = logger
end
# Performs the search for MTR files and returns an array with the found
# files.
# @return [Array<Pathname>] The array of found MTR files.
# @raise [Dragnet::Errors::NoMTRFilesFoundError] If no MTR files are found.
- 1
def files
- 7
@files ||= find_files
end
- 1
private
# Raises an +ArgumentError+ with the appropriate message.
# @param [String] name The name of the missing parameter.
# @raise [ArgumentError] Is always raised with the appropriate message.
- 1
def missing_parameter(name)
- 2
raise ArgumentError, "Missing required parameter #{name}"
end
# Raises an +ArgumentError+ with the appropriate message.
# @param [String] name The name of the parameter with an incompatible type.
# @param [String, Class] expected The expected parameter type.
# @param [String, Class] given The given parameter type.
# @raise [ArgumentError] Is always raised with the appropriate message.
- 1
def incompatible_parameter(name, expected, given)
- 3
raise ArgumentError, "Incompatible parameter type #{name}. Expected: #{expected}, given: #{given}"
end
# Validates the given path
# @param [Object] path The path to validate
# @raise [ArgumentError] If the given path is nil or is not a +Pathname+.
- 1
def validate_path(path)
- 13
missing_parameter('path') unless path
- 12
return if path.is_a?(Pathname)
- 1
incompatible_parameter('path', Pathname, path.class)
end
# Validates the given glob patterns
# @param [String, Array<String>] glob_patterns The glob patterns
# @raise [ArgumentError] If +glob_patterns+ is +nil+ or it isn't an array
# of strings.
- 1
def validate_patterns(glob_patterns)
- 11
missing_parameter('glob_patterns') unless glob_patterns
- 10
return if glob_patterns.is_a?(String)
- 16
return if glob_patterns.is_a?(Array) && glob_patterns.all? { |value| value.is_a?(String) }
- 2
incompatible_parameter('glob_patterns', 'String or Array<String>', glob_patterns.class)
end
# Logs the MTR files that were found.
# @param [Array<Pathname>] files The found MTR files.
# @return [Array<Pathname>] The same array given in +files+.
- 1
def log_found_files(files)
- 27
files.each { |file| logger.info("Found MTR file: #{file}") }
end
# Searches the +path+ for MTR files using the +glob_patterns+
# @return [Array<Pathname>] The array of found MTR files.
# @raise [Dragnet::Errors::NoMTRFilesFoundError] If no MTR files are found.
- 1
def find_files
- 7
logger.info 'Searching for Manual Test Records...'
- 7
files = []
- 7
glob_patterns.each do |glob_pattern|
- 11
logger.info "Globbing #{path} with #{glob_pattern}..."
- 11
files += log_found_files(path.glob(glob_pattern))
end
- 7
return files if files.any?
- 1
raise Dragnet::Errors::NoMTRFilesFoundError,
"No MTR Files found in #{path} with the following glob patterns: #{glob_patterns.join(', ')}"
end
end
end
# frozen_string_literal: true
- 1
require_relative 'errors/unable_to_write_report_error'
- 1
require_relative 'errors/unknown_export_format_error'
- 1
require_relative 'exporters/html_exporter'
- 1
require_relative 'exporters/json_exporter'
- 1
module Dragnet
# The base exporter class, receives an array of test records, an array of
# errors and an array of file names and exports the results to the given
# files. (For each file the format is deduced from its file name).
- 1
class Exporter
KNOWN_FORMATS = {
- 1
'HTML' => { extensions: %w[.html .htm], exporter: Dragnet::Exporters::HTMLExporter },
'JSON' => { extensions: %w[.json], exporter: Dragnet::Exporters::JSONExporter }
}.freeze
- 1
attr_reader :test_records, :errors, :repository, :targets, :logger
# Creates a new instance of the class.
# @param [Array<Dragnet::TestRecord>] test_records The array of MTRs that
# should be included in the reports.
# @param [Array<Hash>] errors An array of Hashes with the data of the MTR
# files that did not pass the validation process.
# @param [Dragnet::Repository, Dragnet::MultiRepository] repository The
# repository where the MTR files and the source code are stored.
# @param [Array<String>] targets The array of target files. For each of them
# the format of the export will be deduced from the file's extension.
# @param [#info, #debug] logger A logger object to use for output.
- 1
def initialize(test_records:, errors:, repository:, targets:, logger:)
- 13
@test_records = test_records
- 13
@errors = errors
- 13
@repository = repository
- 13
@targets = targets
- 13
@logger = logger
end
# Starts the export process.
# @raise [Dragnet::Errors::UnableToWriteReportError] If one of the target
# files cannot be created, opened, or if the output cannot be written to
# it.
- 1
def export
- 13
logger.info 'Starting export process...'
- 13
log_target_files
- 12
formats.each do |format, targets|
- 20
exporter = KNOWN_FORMATS.dig(format, :exporter).new(
test_records: test_records, errors: errors, repository: repository, logger: logger
)
- 20
text = exporter.export
- 20
write_output(text, format, targets)
end
end
- 1
private
# Writes the given text output with the given format to the given targets.
# @param [String] text The text output to write.
# @param [String] format The format of the target file.
# @param [Array<String>] targets The paths of the target files the output
# should be written to.
# @raise [Dragnet::Errors::UnableToWriteReportError] If one of the target
# files cannot be created, opened, or if the output cannot be written to
# it.
- 1
def write_output(text, format, targets)
- 20
targets.each do |target|
- 22
logger.info "Writing #{format} output to #{target}..."
begin
- 22
start = Time.now
- 22
bytes = File.write(target, text)
- 21
elapsed = Time.now - start
- 21
logger.debug("Ok (#{bytes} bytes written in #{elapsed} seconds)")
rescue SystemCallError => e
- 1
raise Dragnet::Errors::UnableToWriteReportError,
"Unable to write report output to #{target}: #{e.message}"
end
end
end
# Writes a log entry with the files that will be written as a result of the
# export process (each with its corresponding format).
- 1
def log_target_files
- 13
files_with_formats = formats.flat_map do |format, targets|
- 42
targets.map { |target| "\t * #{target} as #{format}" }
end
- 12
logger.debug "Target files are:\n#{files_with_formats.join("\n")}"
end
# @return [Hash] A hash whose keys are known formats and whose values are
# arrays of target files.
- 1
def formats
- 25
@formats ||= deduce_formats
end
# Deduces the format of each target file (given its extension) and relates
# them to their corresponding formats.
# @return [Hash] A hash whose keys are known formats and whose values are
# arrays of target files.
- 1
def deduce_formats
- 13
formats = {}
- 13
targets.each do |target|
- 24
extension = File.extname(target).downcase
- 57
format, = KNOWN_FORMATS.find { |_name, config| config[:extensions].include?(extension) }
- 24
unknown_format_error(extension) unless format
- 23
formats[format] ||= []
- 23
formats[format] << target
end
- 12
formats
end
# Raises a +Dragnet::Errors::UnknownExportFormatError+ with the proper error
# message.
# @param [String] extension The extension of the given target file.
# @raise [Dragnet::Errors::UnknownExportFormatError] is always raised.
- 1
def unknown_format_error(extension)
- 3
allowed_extensions = KNOWN_FORMATS.flat_map { |_format, config| config[:extensions] }
- 1
raise Dragnet::Errors::UnknownExportFormatError,
"Unknown export format: '#{extension}'. Valid export formats are: "\
"#{allowed_extensions.join(', ')}"
end
end
end
# frozen_string_literal: true
- 1
require_relative 'exporters/exporter'
- 1
require_relative 'exporters/html_exporter'
- 1
require_relative 'exporters/id_generator'
- 1
require_relative 'exporters/json_exporter'
- 1
require_relative 'exporters/serializers'
- 1
module Dragnet
# Namespace for the exporters: classes that produce files, or reports out of
# the results of the Manual Test Record verification execution.
- 1
module Exporters; end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Exporters
# Base class for all exporter classes.
- 1
class Exporter
- 1
attr_reader :test_records, :errors, :repository, :logger
# @param [Array<Hash>] test_records The array of test records.
# @param [Array<Hash>] errors The array of errors.
# @param [Dragnet::Repository, Dragnet::MultiRepository] repository The
# repository where the MTR files and the source code are stored.
# @param [#info] logger A logger object to use for output.
- 1
def initialize(test_records:, errors:, repository:, logger:)
- 65
@test_records = test_records
- 65
@errors = errors
- 65
@repository = repository
- 65
@logger = logger
end
# @raise [NotImplementedError] Is always raised. Subclasses are expected
# to override this method.
- 1
def export
- 1
raise NotImplementedError,
"'export' method not implemented for class #{self.class}"
end
end
end
end
# frozen_string_literal: true
- 1
require 'erb'
- 1
require_relative '../helpers/repository_helper'
- 1
require_relative 'exporter'
- 1
module Dragnet
- 1
module Exporters
# Creates an HTML report from the given Test Records and Errors data.
- 1
class HTMLExporter < Dragnet::Exporters::Exporter
- 1
include Dragnet::Helpers::RepositoryHelper
- 1
TEMPLATE = File.join(__dir__, 'templates', 'template.html.erb').freeze
# Generates the report and returns it as a string.
# @return [String] The generated HTML report.
- 1
def export
- 31
logger.info "Generating HTML report from template: #{TEMPLATE}..."
- 31
ERB.new(File.read(TEMPLATE)).result(binding)
end
- 1
private
# Returns the percentage that +num1+ represents with respect to +num2+
# @param [Integer, Float] num1 A number.
# @param [Integer, Float] num2 A number.
# @return [Integer, Float] The percentage that +num1+ represents with
# respect to +num2+ rounded to two decimal places.
- 1
def percentage(num1, num2)
- 34
return 0.0 if num1.zero? || num2.zero?
- 13
((num1.to_f / num2) * 100).round(2)
end
# @param [Dragnet::Repository] repository The repository whose branches
# should be retrieved.
# @return [Array<String>] An array with the names of the branches that
# "contain" the current head of the repository (may be empty).
- 1
def software_branches(repository)
# (uniq needed because of remote/local branches)
- 11
repository.branches_with_head.map(&:name).uniq
rescue Git::GitExecuteError => e
logger.warn "Failed to read branches information from the repository at #{repository.path}"
logger.warn e.message
[]
end
# Method used to memoize the output of the +group_by_requirement+ method.
# @see #group_by_requirement
# @return [Hash] A hash whose keys are the requirement IDs and whose
# values are arrays of MTRs
- 1
def test_records_by_requirement
- 10
@test_records_by_requirement ||= group_by_requirement
end
# Groups the MTRs by the requirement(s) they are covering, if a MTR covers
# more than one requirement it will be added to all of them, if a
# requirement is covered by more than one MTR the requirement will end up
# with more than one MTR, example:
#
# {
# 'ESR_REQ_9675' => [MTR1],
# 'ESR_REQ_1879' => [MTR2, MTR3]
# 'ESR_REQ_4714' => [MTR3]
# }
#
# @return [Hash] A hash whose keys are the requirement IDs and whose
# values are arrays of MTRs
- 1
def group_by_requirement
- 10
tests_by_requirement = {}
- 10
test_records.each do |test_record|
- 33
ids = *test_record.id
- 33
ids.each do |id|
- 8
tests_by_requirement[id] ||= []
- 8
tests_by_requirement[id] << test_record
end
end
- 10
tests_by_requirement
end
# Returns the HTML code needed to render the Review Status of a MTR as a
# badge.
# @param [Dragnet::TestRecord] test_record The Test Record.
# @return [String] The HTML code to display the Test Record's review
# status as a badge.
- 1
def review_status_badge(test_record)
- 57
if test_record.review_status
- 2
color = test_record.reviewed? ? 'green' : 'red'
- 2
review_status = test_record.review_status.capitalize
else
- 55
color = 'gray'
- 55
review_status = '(unknown)'
end
- 57
badge_html(color, review_status)
end
# Returns the HTML code needed to display the verification result of a MTR
# (the color and the text inside the badge are picked in accordance to the
# given result).
# @param [Dragnet::VerificationResult] verification_result The result of
# the verification for a given +TestRecord+
# @return [String] The HTML code needed to display the result as a badge.
- 1
def verification_result_badge(verification_result)
- 57
badge_html(
verification_result_color(verification_result),
verification_result.status.capitalize
)
end
# Returns a color that depends on the verification result for a Test
# Record. To be used on HTML elements.
# @param [Dragnet::VerificationResult] verification_result The
# +VerificationResult+ object.
# @return [String] The corresponding color (depends on the +status+ field
# of the +VerificationResult+ object).
- 1
def verification_result_color(verification_result)
- 87
case verification_result.status
when :passed
- 29
'green'
when :skipped
- 29
'yellow'
else
- 29
'red'
end
end
# Returns the color that should be used for the highlight line on the left
# of the card given the result of the MTR's verification.
# @param [Dragnet::VerificationResult] verification_result The
# +VerificationResult+ object associated with the +TestRecord+ being
# rendered on the card.
- 1
def card_color(verification_result)
- 30
verification_result_color(verification_result)
end
# Returns the HTML string to produce a Badge
# @param [String] color The color of the badge.
# @param [String] text The text that goes inside the badge.
# @return [String] The HTML code to produce a badge with the given color
# and text.
- 1
def badge_html(color, text)
- 114
"<span class=\"badge bg-#{color}\">#{text}</span>"
end
# Converts the ID (+String+) or IDs (+Array<String>+) of a +TestRecord+
# object into a string that can be safely rendered in the HTML report.
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object.
# @return [String] A string with the ID or IDs of the +TestRecord+ object.
- 1
def test_record_id_to_string(test_record)
- 57
Array(test_record.id).join(', ')
end
end
end
end
# frozen_string_literal: true
- 1
require 'digest'
- 1
require_relative '../helpers/repository_helper'
- 1
module Dragnet
- 1
module Exporters
# Generates unique IDs for the Manual Test Records by hashing some of their
# properties into a hexadecimal SHA1.
- 1
class IDGenerator
- 1
include Dragnet::Helpers::RepositoryHelper
- 1
attr_reader :repository
# @param [Dragnet::Repository] repository The repository where the MTR
# files are located. This allows the SHA1 to be calculated with relative
# paths to the MTRs' files.
- 1
def initialize(repository)
- 23
@repository = repository
end
# Calculates the ID of the given MTR
# @param [Dragnet::TestRecord] test_record The record for which the ID
# should be calculated.
# @return [String] The ID for the given +TestRecord+.
# :reek:FeatureEnvy (Cannot be done in the TestRecord itself because it needs the Repository)
- 1
def id_for(test_record)
- 65
string = "#{relative_to_repo(test_record.source_file)}#{test_record.id}"
# noinspection RubyMismatchedReturnType (This is never nil)
- 65
Digest::SHA1.hexdigest(string)[0...16]
end
end
end
end
# frozen_string_literal: true
- 1
require 'json'
- 1
require_relative 'exporter'
- 1
require_relative 'id_generator'
- 1
require_relative 'serializers/test_record_serializer'
- 1
module Dragnet
- 1
module Exporters
# Exports the results for the Manual Test Record verification to a JSON
# string.
- 1
class JSONExporter < ::Dragnet::Exporters::Exporter
# @return [String] A JSON string containing an array of objects, one for
# each Test Record.
- 1
def export
- 33
logger.info 'Exporting data to JSON'
- 33
test_records.map do |test_record|
- 87
::Dragnet::Exporters::Serializers::TestRecordSerializer
.new(test_record, repository).serialize
.merge(id: id_generator.id_for(test_record))
end.to_json
end
- 1
private
# @return [Dragnet::Exporters::IDGenerator] An instance of the IDGenerator
# class that can be used to calculate the ID for the exported MTRs.
- 1
def id_generator
- 87
@id_generator ||= ::Dragnet::Exporters::IDGenerator.new(repository)
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'serializers/repo_serializer'
- 1
require_relative 'serializers/test_record_serializer'
- 1
require_relative 'serializers/verification_result_serializer'
- 1
module Dragnet
- 1
module Exporters
# Namespace for the serializer classes used by the exporters.
- 1
module Serializers; end
end
end
# frozen_string_literal: true
- 1
require 'active_support'
- 1
require 'active_support/core_ext/object/blank'
- 1
module Dragnet
- 1
module Exporters
- 1
module Serializers
# Serializes a +Repo+ object into a +Hash+
- 1
class RepoSerializer
- 1
attr_reader :repo
# @param [Dragnet::Repo] repo The +Repo+ object to serialize.
- 1
def initialize(repo)
- 27
@repo = repo
end
# Serializes the given +Repo+ object.
# @return [Hash] A +Hash+ representing the given +Repo+ object.
- 1
def serialize
{
- 27
path: repo.path,
sha1: repo.sha1
}.tap do |hash|
- 27
hash[:files] = serialize_files if repo.files.present?
end
end
- 1
private
# Serializes the array of files attached to the +Repo+
# @return [Array<String>] The array of file names (without the path to
# the repository).
- 1
def serialize_files
- 89
repo.files.map { |file| file.to_s.gsub("#{repo.path}/", '') }
end
end
end
end
end
# frozen_string_literal: true
- 1
require 'active_support'
- 1
require 'active_support/core_ext/object/blank'
- 1
require_relative '../../helpers/repository_helper'
- 1
require_relative 'repo_serializer'
- 1
require_relative 'verification_result_serializer'
- 1
module Dragnet
- 1
module Exporters
- 1
module Serializers
# Serializes a +TestRecord+ object into a +Hash+.
- 1
class TestRecordSerializer
- 1
include ::Dragnet::Helpers::RepositoryHelper
- 1
attr_reader :test_record, :repository
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# serialize.
# @param [Dragnet::RepositoryBase] repository The +Repository+ object
# associated with the +TestRecord+. Used to render file paths relative
# to the repository instead of as absolute paths.
- 1
def initialize(test_record, repository)
- 96
@test_record = test_record
- 96
@repository = repository
end
# rubocop:disable Metrics/AbcSize (because of the Hash)
# rubocop:disable Metrics/CyclomaticComplexity (because of the conditionals)
# rubocop:disable Metrics/PerceivedComplexity (because of the conditionals)
# rubocop:disable Metrics/MethodLength (because of he Hash)
# @return [Hash] A +Hash+ representing the given +TestRecord+ object.
- 1
def serialize
{
- 96
refs: Array(test_record.id),
result: test_record.result,
review_status: render_review_status,
mtr_file: relative_to_repo(test_record.source_file).to_s,
verification_result: serialized_verification_result,
# TODO: Remove the started_at and finished_at attributes after solving
# https://esrlabs.atlassian.net/browse/JAY-493
started_at: serialized_verification_result[:started_at],
finished_at: serialized_verification_result[:finished_at]
}.tap do |hash|
- 96
hash[:sha1] = test_record.sha1 if test_record.sha1.present?
- 96
hash[:owner] = Array(test_record.name).join(', ') if test_record.name.present?
- 96
hash[:description] = test_record.description if test_record.description.present?
- 96
hash[:test_method] = Array(test_record.test_method) if test_record.test_method.present?
- 96
if test_record.tc_derivation_method.present?
- 2
hash[:tc_derivation_method] = Array(test_record.tc_derivation_method)
end
- 96
hash[:review_comments] = test_record.review_comments if test_record.review_comments.present?
- 96
hash[:findings] = test_record.findings if test_record.findings?
- 96
hash[:files] = serialize_files if test_record.files.present?
- 96
hash[:repos] = serialize_repos if test_record.repos.present?
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
- 1
private
# Renders the +TestRecord+'s review status
# @return [String] The review status, either +'not_reviewed'+ or +'reviewed'+
- 1
def render_review_status
- 96
"#{test_record.reviewed? ? nil : 'not_'}reviewed"
end
# Serializes the files listed in the given +TestRecord+
# @return [Array<String>] An array of strings, one for each listed file.
- 1
def serialize_files
- 88
test_record.files.map { |file| relative_to_repo(file).to_s }
end
# Serializes the +Repo+ objects attached to the +TestRecord+
# @return [Array<Hash>] An array of +Hash+es representing each of the
# +Repo+ objects associated with the +TestRecord+
- 1
def serialize_repos
- 46
test_record.repos.map { |repo| ::Dragnet::Exporters::Serializers::RepoSerializer.new(repo).serialize }
end
# Serializes the +VerificationResult+ object attached to the given
# +TestRecord+
# @return [Hash] A +Hash+ representation of the +VerificationResult+
# object.
- 1
def serialized_verification_result
- 288
@serialized_verification_result ||= ::Dragnet::Exporters::Serializers::VerificationResultSerializer.new(
test_record.verification_result
).serialize
end
end
end
end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Exporters
- 1
module Serializers
# Serializes a +VerificationResult+ object into a +Hash+.
- 1
class VerificationResultSerializer
- 1
attr_reader :verification_result
# Format used to serialize the +VerificationResult+'s date/time attributes.
- 1
DATE_FORMAT = '%F %T %z'
# @param [Dragnet::VerificationResult] verification_result The
# +VerificationResult+ object to serialize.
- 1
def initialize(verification_result)
- 75
@verification_result = verification_result
end
# @return [Hash] The +Hash+ representation of the given
# +VerificationResult+ object.
- 1
def serialize
{
- 75
status: verification_result.status,
started_at: verification_result.started_at.strftime(DATE_FORMAT),
finished_at: verification_result.finished_at.strftime(DATE_FORMAT),
runtime: verification_result.runtime
}.tap do |hash|
- 75
hash[:reason] = verification_result.reason if verification_result.reason
end
end
end
end
end
end
# frozen_string_literal: true
- 1
module Dragnet
- 1
module Helpers
# Some helper methods to use when working with repositories.
- 1
module RepositoryHelper
# @return [String] The first 10 characters of the given string (normally
# used to shorten SHA1s when building messages).
- 1
def shorten_sha1(sha1)
- 45
sha1[0...10]
end
# @return [Pathname] The base path of the repository where the MTR and the
# source files are located. Used to present relative paths.
- 1
def repo_base
- 301
@repo_base ||= repository.path
end
# Transforms the given path into a path relative to the repository's root
# @param [Pathname] path The absolute path.
# @return [Pathname] A path relative to the repository's root.
- 1
def relative_to_repo(path)
- 301
path.relative_path_from(repo_base)
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'base_repository'
- 1
module Dragnet
# This is a dummy class that acts as a placeholder when Dragnet is executed
# on a multi-repo set-up. Since there is no Git repository in the directory
# where git-repo runs git commands cannot be executed there only in the inner
# repositories.
#
# This class's job is to raise a particular error when a git operation is
# attempted directly on this directory so that Dragnet can recognize the cause
# of the error and display it correctly.
#
# It also acts as a collection of repositories. It stores a collection of
# +Dragnet::Repository+ objects, which point to the actual repositories (this
# is just so that the same repository isn't initialized multiple times).
- 1
class MultiRepository < Dragnet::BaseRepository
- 1
attr_reader :repositories
# @param [Pathname] path Path to the directory where the inner repositories
# reside.
- 1
def initialize(path:)
- 37
super
- 37
@repositories = {}
end
# @return [TrueClass] It always returns true
- 1
def multi?
- 1
true
end
- 1
private
# @param [Symbol] method_name The name of the method that was invoked.
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
# with a description of the method that was invoked and a possible cause
# for the failure.
- 1
def incompatible_repository(method_name)
- 8
super(
"Failed to perform the action '#{method_name}' on '#{path}'."\
" There isn't a git repository there. If you are running with the"\
' --multi-repo command line switch make sure that all of your MTRs'\
" contain a valid 'repos' attribute."
)
end
end
end
# frozen_string_literal: true
- 1
require_relative 'validators/entities/repo_validator'
- 1
module Dragnet
# Represents a repository, (for MTRs which reference multiple repositories in
# a multi-repo project, often managed with git-repo)
- 1
class Repo
- 1
attr_accessor :path, :sha1, :files
# @param [Hash] args The data for the Repo
# @option args [String] :path The path where the repository is stored.
# @option args [String] :sha1 The SHA1 the repository had when the MTR was
# created.
# @option args [String, Array<String>, nil] :files The file or array of
# files covered by the MTR.
- 1
def initialize(args)
- 25
@path = args[:path]
- 25
@sha1 = args[:sha1]
- 25
@files = args[:files]
end
# Validates the +Repo+ instance (by checking each of its attributes).
# @raise [Dragnet::Errors::ValidationError] If any of the attributes of the
# +Repo+ object is invalid.
# @see Dragnet::Validators::Entities::RepoValidator#validate
- 1
def validate
- 2
Dragnet::Validators::Entities::RepoValidator.new(self).validate
end
end
end
# frozen_string_literal: true
- 1
require 'forwardable'
- 1
require 'git/url'
- 1
require_relative 'base_repository'
- 1
module Dragnet
# A small wrapper around a Git Repository object. It provides some useful
# methods needed during the verify process as well as for reporting.
- 1
class Repository < Dragnet::BaseRepository
- 1
extend Forwardable
- 1
attr_reader :git
- 1
def_delegators :@git, :branch, :branches, :diff
# Creates a new instance of the class. Tries to open the given path as a Git
# repository.
# @param [Pathname] path The path where the root of the repository is located.
# @raise [ArgumentError] If the given path is not a valid git repository.
- 1
def initialize(path:)
- 35
super
- 35
@git = Git.open(path)
end
# @return [Git::Object::Commit] The +Commit+ object at the +HEAD+ of the
# repository.
- 1
def head
- 33
@head ||= git.object('HEAD')
end
# Returns the URI path of the repository (extracted from its first remote
# [assumed to be the origin]). Example:
#
# ssh://jenkins@gerrit.int.esrlabs.com:29418/tools/dragnet -> /tools/dragnet
#
# @return [String] The URI path of the repository
- 1
def remote_uri_path
- 16
Git::URL.parse(git.remotes.first.url).path
end
# @return [FalseClass] It always returns false
- 1
def multi?
- 19
false
end
# Returns an array of all the branches that include the given commit.
# @param [String] commit The SHA1 of the commit to look for.
# @return [Array<Git::Branch>] An array with all the branches that contain
# the given commit.
- 1
def branches_with(commit)
- 63
branches.select { |branch| branch.contains?(commit) }
end
# Returns an array of all the branches that include the current HEAD.
# @return [Array<Git::Branch>] An array with all the branches that contain
# the current HEAD.
- 1
def branches_with_head
- 14
@branches_with_head ||= branches_with(head.sha)
end
- 1
private
# @param [Symbol] method_name The name of the method that was invoked.
# @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
# with a description of the method that was invoked and a possible cause
# for the failure.
- 1
def incompatible_repository(method_name)
- 1
super(
"Failed to perform the action '#{method_name}' on '#{path}'."\
' The path was not set-up as a multi-repo path. If you are running'\
' without the --multi-repo command line switch make sure that none of'\
" your MTRs have a 'repos' attribute or run with the --multi-repo switch"
)
end
end
end
# frozen_string_literal: true
- 1
require_relative 'validators/entities/test_record_validator'
- 1
module Dragnet
# Represents a Manual Test Record loaded from a MTR file.
- 1
class TestRecord
- 1
PASSED_RESULT = 'passed'
- 1
REVIEWED_STATUS = 'reviewed'
- 1
NO_FINDINGS = 'no findings'
# :reek:Attribute (This is an entity class)
- 1
attr_accessor :id, :result, :sha1, :name, :description, :files, :repos,
:review_status, :review_comments, :findings, :test_method,
:tc_derivation_method, :source_file, :verification_result
# rubocop:disable Metrics/AbcSize (There isn't much that can be done here,
# those are the attributes an MTR has).
# :reek:FeatureEnvy (Refers to args as much as it refers to itself)
# Creates a new instance of the class.
# @param [Hash] args The data for the Manual Test Record
# @option args [String] :id The ID of the MTR
# @option args [String] :result The result of the Manual Test.
# @option args [String] :sha1 The SHA1 of the commit in which the Manual
# Test was performed.
# @option args [String, Array<String>, nil] :name The name of the person who
# performed the Manual Test.
# @option args [String, nil] :description The description of the Manual
# Test, normally which actions were performed and what it was mean to
# test.
# @option args [String, Array<String>, nil] :files The files involved in the
# MTR, these are the files which will be checked for changes when
# evaluating the validity of the MTR.
# @option args [Array<Hash>, nil] :repos An array of +Hash+es with the
# information about the repositories that are involved in the MTR, these
# repositories will be checked for changes during the evaluation of the
# MTR.
# @option args [String, nil] :review_status or :reviewstatus The review
# status of the MTR. (Normally changed when someone other than the tester
# verifies the result of the Manual Test)
# @option args [String, nil] :review_comments or :reviewcomments The
# comments left by the person who performed the review of the Manual Test.
# @option args [String, nil] :findings The findings that the reviewer
# collected during the review process (if any).
# @option args [String, Array<String>, nil] :test_method The method(s) used
# to carry out the test.
# @option args [String, Array<String>, nil] :tc_derivation_method: The
# method(s) used to derive the test case,
# @note Either +:files+ or +:repos+ should be present, not both.
- 1
def initialize(args)
- 144
@id = args[:id]
- 144
@result = args[:result]
- 144
@sha1 = args[:sha1]
- 144
@name = args[:name]
- 144
@description = args[:description]
- 144
@files = args[:files]
- 144
@repos = args[:repos]
- 144
@review_status = args[:review_status] || args[:reviewstatus]
- 144
@review_comments = args[:review_comments] || args[:reviewcomments]
- 144
@findings = args[:findings]
- 144
@test_method = args[:test_method]
- 144
@tc_derivation_method = args[:tc_derivation_method]
end
# rubocop:enable Metrics/AbcSize
# Validates the MTR's fields
# @raise [Dragnet::Errors::ValidationError] If the validation fails.
- 1
def validate
- 2
Dragnet::Validators::Entities::TestRecordValidator.new(self).validate
end
# @return [Boolean] True if the Manual Test passed, false otherwise.
- 1
def passed?
- 2
result == PASSED_RESULT
end
# @return [Boolean] True if the Manual Test Record has been reviewed, false
# otherwise.
- 1
def reviewed?
- 94
review_status == REVIEWED_STATUS
end
# @return [Boolean] True if the Manual Test Record has findings (problems
# annotated during the review), false otherwise.
- 1
def findings?
- 69
!(findings.nil? || findings.strip.empty? || findings.downcase == NO_FINDINGS)
end
end
end
# frozen_string_literal: true
- 1
require 'colorize'
- 1
require_relative 'validators/data_validator'
- 1
require_relative 'validators/files_validator'
- 1
require_relative 'validators/repos_validator'
- 1
module Dragnet
# Validates a set of Manual Test Record files. That means, checking that they
# can be read, that they are valid YAML files, that they have the expected
# keys and that these keys have sensible values.
- 1
class Validator
- 1
attr_reader :files, :path, :logger, :errors, :valid_files
# Creates a new instance of the class.
# @param [Array<Pathname>] files An array with the MTR files to validate.
# @param [Pathname] path The path where the MTR files are located.
# @param [#info, #error] logger A logger object to use for output.
- 1
def initialize(files:, path:, logger:)
- 19
@files = files
- 19
@path = path
- 19
@logger = logger
end
# Validates the given files.
# @return [Array<Dragnet::TestRecord>] An array of +TestRecord+s, one for
# each valid MTR file (invalid files will be added to the +errors+ array).
# The returned hash has the following structure:
- 1
def validate
- 19
logger.info('Validating MTR Files...')
- 19
@errors = []
- 73
@valid_files = files.map { |file| validate_file(file) }.compact
end
- 1
private
# Validates the given file
# @param [Pathname] file The file to be validated.
# @return [Dragnet::TestRecord, nil] A +TestRecord+ object or +nil+ if the
# file is invalid.
# rubocop:disable Metrics/AbcSize (because of logging).
- 1
def validate_file(file)
- 54
logger.info "Validating #{file}..."
- 54
data = YAML.safe_load(File.read(file))
- 50
test_record = Dragnet::Validators::DataValidator.new(data, file).validate
- 44
Dragnet::Validators::FilesValidator.new(test_record, path).validate
- 38
Dragnet::Validators::ReposValidator.new(test_record, path).validate
- 20
logger.info "#{'✔ SUCCESS'.colorize(:light_green)} #{file} Successfully loaded"
- 20
test_record
rescue SystemCallError => e
- 2
push_error(file, 'IO Error: Cannot read the specified file', e)
rescue Psych::Exception => e
- 2
push_error(file, 'YAML Parsing Error', e)
rescue Dragnet::Errors::YAMLFormatError => e
- 6
push_error(file, 'YAML Formatting Error', e)
rescue Dragnet::Errors::FileNotFoundError => e
- 15
push_error(file, 'Referenced file not found in repository', e)
rescue Dragnet::Errors::RepoPathNotFoundError => e
- 9
push_error(file, 'Referenced repository not found', e)
end
# rubocop:enable Metrics/AbcSize
# Pushes an entry into the +errors+ array.
# @param [Pathname] file The file that contains the error.
# @param [String] message A general description of the message.
# @param [Exception] exception The raised exception (through which the file
# was branded invalid)
# @return [nil] Returns nil so that +validate_file+ can return nil for
# invalid files.
- 1
def push_error(file, message, exception)
- 34
errors << { file: file, message: message, exception: exception }
- 34
logger.error "#{'✘ FAILED'.colorize(:light_red)} #{file} Failed: #{message} - #{exception.message}"
nil
end
end
end
# frozen_string_literal: true
- 1
require_relative 'validators/data_validator'
- 1
require_relative 'validators/entities'
- 1
require_relative 'validators/fields'
- 1
require_relative 'validators/files_validator'
- 1
require_relative 'validators/validator'
- 1
module Dragnet
# Namespace for Validator classes.
- 1
module Validators; end
end
# frozen_string_literal: true
- 1
require 'active_support'
- 1
require 'active_support/core_ext/hash/keys'
- 1
require_relative '../test_record'
- 1
require_relative 'validator'
- 1
module Dragnet
- 1
module Validators
# Validates the data (key-value pairs) inside an MTR file. Verifies the
# structure, the required keys and their types.
- 1
class DataValidator < Dragnet::Validators::Validator
- 1
attr_reader :data, :source_file
# Creates a new instance of the class
# @param [Hash] data The data inside the YAML (after parsing)
# @param [Pathname] source_file The path to the file from which the MTR
# data was loaded.
- 1
def initialize(data, source_file)
- 8
@data = data
- 8
@source_file = source_file
end
# Validates the given data
# @return [Dragnet::TestRecord] A +TestRecord+ object created
# from the given data (if the data was valid).
# @raise [Dragnet::Errors::YAMLFormatError] If the data is invalid. The
# raised exceptions contains a message specifying why the data is
# invalid.
- 1
def validate
- 8
yaml_format_error("Incompatible data structure. Expecting a Hash, got a #{data.class}") unless data.is_a?(Hash)
- 7
data.deep_symbolize_keys!
# A call to chomp for strings is needed because the following YAML
# syntax:
#
# findings: >
# no findings
#
# causes the string values to end with a newline ("\n"):
- 35
data.transform_values! { |value| value.is_a?(String) ? value.chomp : value }
- 7
test_record = create_mtr(data)
- 7
validate_mtr(test_record)
end
- 1
private
# @param [Hash] data A hash with the data for the +TestRecord+
# @see Dragnet::TestRecord#initialize
- 1
def create_mtr(data)
- 7
Dragnet::TestRecord.new(data).tap do |test_record|
- 7
test_record.source_file = source_file
end
end
# Creates a +Dragnet::TestRecord+ with the given data and runs its
# validation. If the validation is successful the +TestRecord+
# object is returned, if the validation fails an error is raised.
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# validate.
# @return [Dragnet::TestRecord] The given +TestRecord+ object if the
# validation passed.
# @raise [Dragnet::Errors::YAMLFormatError] If the data is invalid. The
# raised exceptions contains a message specifying why the data is
# invalid.
- 1
def validate_mtr(test_record)
- 7
test_record.validate
- 6
test_record
rescue Dragnet::Errors::ValidationError => e
- 1
yaml_format_error(e.message)
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'entities/repo_validator'
- 1
require_relative 'entities/test_record_validator'
- 1
module Dragnet
- 1
module Validators
# Namespace for Dragnet's Entity Validators (validators that deal with data
# objects / models).
- 1
module Entities; end
end
end
# frozen_string_literal: true
- 1
require_relative '../fields/sha1_validator'
- 1
require_relative '../fields/files_validator'
- 1
require_relative '../fields/path_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Entities
# Validates a +Dragnet::Repo+ object, by checking its attributes.
- 1
class RepoValidator
- 1
attr_reader :repo
# @param [Dragnet::Repo] repo An instance of +Dragnet::Repo+ to validate.
- 1
def initialize(repo)
- 7
@repo = repo
end
# Validates the instance of the +Dragnet::Repo+ object by checking each
# of its attributes.
# @raise [Dragnet::Errors::ValidationError] If any of the fields in the
# given +Dragnet::Repo+ object fails the validation.
- 1
def validate
- 7
Dragnet::Validators::Fields::SHA1Validator.new.validate('repos[sha1]', repo.sha1)
- 6
Dragnet::Validators::Fields::PathValidator.new.validate('repos[path]', repo.path)
- 5
repo.files = Dragnet::Validators::Fields::FilesValidator.new.validate('repos[files]', repo.files)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../fields/description_validator'
- 1
require_relative '../fields/files_validator'
- 1
require_relative '../fields/id_validator'
- 1
require_relative '../fields/meta_data_field_validator'
- 1
require_relative '../fields/repos_validator'
- 1
require_relative '../fields/result_validator'
- 1
require_relative '../fields/sha1_validator'
- 1
require_relative '../../errors/validation_error'
- 1
module Dragnet
- 1
module Validators
- 1
module Entities
# Validates a MTR object
- 1
class TestRecordValidator
- 1
attr_reader :test_record
# Creates a new instance of the class.
# @param [Dragnet::TestRecord] test_record The test record to validate.
- 1
def initialize(test_record)
- 28
@test_record = test_record
end
# Validates the given test record
# @raise [Dragnet::Errors::ValidationError] If the validation fails.
- 1
def validate
- 28
repos_xor_files
- 27
repos_xor_sha1
- 24
Dragnet::Validators::Fields::IDValidator.new.validate('id', test_record.id)
- 23
Dragnet::Validators::Fields::DescriptionValidator.new.validate('description', test_record.description)
- 22
validate_meta_data_fields
- 21
test_record.files = Dragnet::Validators::Fields::FilesValidator.new.validate('files', test_record.files)
- 20
test_record.result = Dragnet::Validators::Fields::ResultValidator.new.validate('result', test_record.result)
end
- 1
private
# @raise [Dragnet::Errors::ValidationError] If the MTR has both a
# +files+ and a +repos+ attribute.
- 1
def repos_xor_files
- 28
return unless test_record.files && test_record.repos
- 1
raise Dragnet::Errors::ValidationError,
"Invalid MTR: #{test_record.id}. Either 'files' or 'repos' should be provided, not both"
end
# Executes the validation over the +repos+ attribute and then verifies
# if the +sha1+ attribute was also given. If it was, an error is
# raised. If +repos+ is not present, then the +sha1+ attribute is
# validated.
#
# This happens in this order to leverage the fact that the
# +ReposValidator+ returns +nil+ for empty +Array+s. So if +repos+ is
# given as en empty +Array+ the MTR will still be considered valid
# (provided it has a SHA1).
#
# @raise [Dragnet::Errors::ValidationError] If the validation of the
# +repos+ attribute fails, if both +repos+ and +sha1+ are present or
# if the validation of the +sha1+ attribute fails.
- 1
def repos_xor_sha1
- 27
test_record.repos = Dragnet::Validators::Fields::ReposValidator.new.validate('repos', test_record.repos)
- 26
unless test_record.repos
- 22
Dragnet::Validators::Fields::SHA1Validator.new.validate('sha1', test_record.sha1)
- 21
return
end
- 4
return unless test_record.sha1
- 1
raise Dragnet::Errors::ValidationError,
"Invalid MTR: #{test_record.id}. Either 'repos' or 'sha1' should be provided, not both"
end
# Validates the meta-data fields of the Test Record.
# @raise [Dragnet::Errors::ValidationError] If any of the meta-data
# fields fail the validation.
- 1
def validate_meta_data_fields
- 22
meta_data_validator = Dragnet::Validators::Fields::MetaDataFieldValidator.new
- 22
test_record.name = meta_data_validator.validate('name', test_record.name)
- 21
test_record.test_method = meta_data_validator.validate('test_method', test_record.test_method)
- 21
test_record.tc_derivation_method = meta_data_validator.validate(
'tc_derivation_method', test_record.tc_derivation_method
)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'fields/description_validator'
- 1
require_relative 'fields/files_validator'
- 1
require_relative 'fields/id_validator'
- 1
require_relative 'fields/meta_data_field_validator'
- 1
require_relative 'fields/path_validator'
- 1
require_relative 'fields/repos_validator'
- 1
require_relative 'fields/result_validator'
- 1
require_relative 'fields/sha1_validator'
- 1
module Dragnet
- 1
module Validators
# Namespace module for entity fields validators.
- 1
module Fields; end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the +description+ field for a MTR.
- 1
class DescriptionValidator < Dragnet::Validators::Fields::FieldValidator
# Validates a MTR's description
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @raise [Dragnet::Errors::ValidationError] If the description contains
# anything but a +String+ or +nil+.
# :reek:NilCheck (Only +nil+ is allowed, +false+ should be considered invalid).
- 1
def validate(key, value)
- 6
return if value.nil?
- 5
validate_type(key, value, String)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../../errors/validation_error'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Base class for all the validators used to validate individual fields
# inside entities.
- 1
class FieldValidator
- 1
def validate(_key, _value)
- 1
raise NotImplementedError, "#validate method not implemented in #{self.class}"
end
- 1
private
# Validates the presence of a value
# @param [String, Symbol] key The key associated with the value.
# @param [Object] value The value to validate.
# @raise [Dragnet::Errors::ValidationError] If the given value is not
# present (i.e. is +nil+)
- 1
def validate_presence(key, value)
- 19
validation_error("Missing required key: #{key}") if value.nil?
end
# Validates the type of the given value.
# @param [String, Symbol] key The key associated with the value.
# @param [Object] value The value to validate.
# @param [Array<Class>] expected_types The allowed types for the given
# value.
# @raise [Dragnet::Errors::ValidationError] If the given value has a type
# which is not in the given array of expected types.
- 1
def validate_type(key, value, *expected_types)
- 43
return if expected_types.include?(value.class)
- 11
validation_error(
"Incompatible type for key #{key}: "\
"Expected #{expected_types.join(', ')} got #{value.class} instead"
)
end
# Raises a +Dragnet::Errors::ValidationError+ with the given message.
# @param [String] message The message for the error.
# @raise [Dragnet::Errors::ValidationError] Is always raised.
- 1
def validation_error(message)
- 24
raise Dragnet::Errors::ValidationError, message
end
# Validates that all elements inside the given array are of the
# expected type
# @param [String, Symbol] key The key associated with the array.
# @param [Array] array The array whose types should be checked.
# @param [Class] expected_type The type the elements inside the array
# should have.
# @raise [Dragnet::Errors::ValidationError] If any of the elements inside
# the given array is of a different type.
- 1
def validate_array_types(key, array, expected_type)
- 42
incompatible_value = array.find { |val| !val.is_a?(expected_type) }
- 12
return unless incompatible_value
- 4
validation_error(
"Incompatible type for key #{key}: Expected a Array<#{expected_type}>. "\
"Found a(n) #{incompatible_value.class} inside the array"
)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the files field on a Manual Test Record
- 1
class FilesValidator < Dragnet::Validators::Fields::FieldValidator
# Validates the MTR's +files+ array.
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @return [Array<String>, nil] If +files+ is an Array or a String then
# an array is returned, if +files+ is +nil+ then +nil+ is returned.
# @raise [Dragnet::Errors::ValidationError] If the +files+ key is not a
# +String+ or an +Array+ of +String+s.
- 1
def validate(key, value)
- 4
return unless value
- 4
validate_type(key, value, String, Array)
- 3
value = *value
- 3
validate_array_types(key, value, String)
- 2
value
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the ID Field for Manual Test Records
- 1
class IDValidator < Dragnet::Validators::Fields::FieldValidator
# Validates the Requirement ID(s) of the MTR
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @raise [Dragnet::Errors::ValidationError] If the Requirement ID(s) are
# missing, they are not a String or an Array of Strings if they contain
# a disallowed character or (in the case of an Array) any of its
# elements is not a String.
- 1
def validate(key, value)
- 5
validate_presence(key, value)
- 4
validate_type(key, value, String, Array)
- 3
if value.is_a?(String)
- 2
match = value.match(/,|\s/)
- 2
return unless match
- 1
validation_error(
"Disallowed character '#{match}' found in the value for key #{key}. "\
'To use multiple requirement IDs please put them into an array'
)
else
- 1
validate_array_types(key, value, String)
end
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
require_relative '../../errors/validation_error'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Base class to validate the fields that are part of the meta-data group.
# This means: Either +String+ +Array<String>+ or +nil+ as value.
- 1
class MetaDataFieldValidator < Dragnet::Validators::Fields::FieldValidator
# Validates the specified attribute as a meta-data field.
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @raise [Dragnet::Errors::ValidationError] If the attribute fails the
# validation.
# @return [nil] If +value+ is +nil+ or an empty array.
# @return [Array<String>] If +value+ is a +String+ or an +Arry<String>+
- 1
def validate(key, value)
- 10
return unless value
- 8
validate_type(key, value, String, Array)
- 7
if value.is_a?(Array)
- 5
return if value.empty?
- 3
validate_array_types(key, value, String)
- 2
value
else
- 2
[value]
end
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the +path+ attribute of a +Repo+ object.
- 1
class PathValidator < Dragnet::Validators::Fields::FieldValidator
# Validates the Path of the repository.
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @raise [Dragnet::Errors::ValidationError] If the path is missing, or
# it isn't a String.
- 1
def validate(key, value)
- 3
validate_presence(key, value)
- 2
validate_type(key, value, String)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../../repo'
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates that the +repos+ attribute in an MTR is valid. This means:
# * It is either a +Hash+ or an +Array+ of +Hash+es.
# * The attributes inside each of the +Hash+es are also valid.
- 1
class ReposValidator < Dragnet::Validators::Fields::FieldValidator
# Validates the MTR's +repos+ field.
# @param [String] key The name of the key (usually +'repos'+)
# @param [Object] value The value associated to the attribute.
# @return [Array<Dragnet::Repo>, nil] If +value+ is a valid +Hash+ or a
# valid +Array+ of +Hash+es an +Array+ of +Dragnet::Repo+ objects is
# returned. If +value+ is +nil+, +nil+ is returned.
# @raise [Dragnet::Errors::ValidationError] If +value+ is not a +Hash+
# or an +Array+ of +Hash+es or the attributes inside the +Hash+es are
# invalid.
# @see Dragnet::Repo#validate
- 1
def validate(key, value)
- 12
return unless value
- 11
validate_type(key, value, Hash, Array)
- 10
if value.is_a?(Array)
- 6
return if value.empty?
- 5
validate_array_types(key, value, Hash)
else
# This is needed because trying to apply the splat operator over a
# Hash will result in an Array of Arrays (one for each of the Hash's
# key pairs).
- 4
value = [value]
end
- 8
create_repos(value)
end
- 1
private
# @param [Array<Hash>] hashes The array of +Hash+es from which the Repo
# objects shall be created.
# @return [Array<Dragnet::Repo>] The array of +Dragnet::Repo+ objects
# that result from using each of the given +Hash+es as parameters for
# the constructor.
# @raise [Dragnet::Errors::ValidationError] If the attributes inside the
# +Hash+es are invalid.
# @see Dragnet::Repo#validate
- 1
def create_repos(hashes)
- 8
hashes.map do |hash|
- 12
repo = Dragnet::Repo.new(**hash)
- 12
repo.validate
- 12
repo
end
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the result field of an MTR Record
- 1
class ResultValidator < Dragnet::Validators::Fields::FieldValidator
- 1
VALID_RESULTS = %w[passed failed].freeze
# Validates the MTR's result
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @return [String] The downcase version of the result field.
# @raise [Dragnet::Errors::ValidationError] If the result is missing, if
# it isn't a String or is not one of the allowed values for the field.
- 1
def validate(key, value)
- 5
validate_presence(key, value)
- 4
validate_type(key, value, String)
- 3
value = value.downcase
- 3
return value if VALID_RESULTS.include?(value)
- 1
validation_error(
"Invalid value for key result: '#{value}'. "\
"Valid values are #{VALID_RESULTS.join(', ')}"
)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'field_validator'
- 1
module Dragnet
- 1
module Validators
- 1
module Fields
# Validates the SHA1 field of a Manual Test Record
- 1
class SHA1Validator < Dragnet::Validators::Fields::FieldValidator
- 1
SHA1_MIN_LENGTH = 7
- 1
SHA1_MAX_LENGTH = 40
- 1
SHA1_REGEX = /\A[0-9a-f]+\Z/.freeze
# Validates the SHA1 of the MTR
# @param [String] key The name of the key
# @param [Object] value The value of the key
# @raise [Dragnet::Errors::ValidationError] If the SHA1 is missing, is not
# and string, is too short or too long or is not a valid hexadecimal
# string.
- 1
def validate(key, value)
- 6
validate_presence(key, value)
- 5
validate_type(key, value, String)
- 4
length = value.length
- 4
unless length >= SHA1_MIN_LENGTH && length <= SHA1_MAX_LENGTH
- 2
validation_error(
"Invalid value for key #{key}: '#{value}'. Expected a string between "\
"#{SHA1_MIN_LENGTH} and #{SHA1_MAX_LENGTH} characters"
)
end
- 2
return if value.match(SHA1_REGEX)
- 1
validation_error(
"Invalid value for key #{key}: '#{value}'. "\
"Doesn't seem to be a valid hexadecimal string"
)
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../errors/file_not_found_error'
- 1
require_relative 'validator'
- 1
module Dragnet
- 1
module Validators
# Validates the +files+ key in the given Manual Test Record object.
# Validates:
# - That the listed file(s) glob pattern(s) match at least one file in the
# repository.
- 1
class FilesValidator < Dragnet::Validators::Validator
- 1
attr_reader :test_record, :path
# Creates a new instance of the class.
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object whose
# files should be validated.
# @param [Pathname] path The path to the repository where the files are
# supposed to be located.
- 1
def initialize(test_record, path)
- 6
@test_record = test_record
- 6
@files = test_record.files
- 6
@path = path
end
# Validates the +files+ key in the given data.
# Updates the +file+ key in the given +data+ to the actual files found in
# the repository.
# @raise [Dragnet::Errors::FileNotFoundError] If any of the listed files
# cannot be found in the given repository path or if a glob pattern
# doesn't match any files there.
- 1
def validate
- 6
return unless files
- 5
test_record.files = translate_paths && force_relative_paths && resolve_files
end
- 1
private
- 1
attr_reader :files
# Forces all the file paths to be relative by removing the +/+ at the
# start (if they have one). This is done to ensure that files are always
# considered relative to the path being checked.
- 1
def force_relative_paths
- 5
@files = files.map do |file|
- 13
file.sub(%r{^/}, '')
end
end
# Translate the file paths from windows style paths (with \ as path
# separator) to Unix style paths (with / as path separator).
# This is done so that the git commands work in all systems.
- 1
def translate_paths
- 5
@files = files.map do |file|
- 13
file.tr('\\', '/')
end
end
# Resolve the given files by checking for matches in the given repository.
# Glob patterns are resolved an translated into individual files.
# @return [Array<Pathname>] The resolved file paths.
# @raise [Dragnet::Errors::FileNotFoundError] If any of the listed files
# cannot be found in the given repository path or if a glob pattern
# doesn't match any files there.
- 1
def resolve_files
- 5
resolved_files = []
- 5
files.each do |file|
# Files can be defined as glob patterns
- 11
matched_files = path.glob(file)
- 11
if matched_files.empty?
- 1
raise Dragnet::Errors::FileNotFoundError,
"Could not find any files matching #{file} in #{path}"
end
- 10
resolved_files += matched_files
end
- 4
resolved_files
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../errors/repo_path_not_found_error'
- 1
require_relative 'files_validator'
- 1
module Dragnet
- 1
module Validators
# Validates the +Repo+ objects attached to the given +TestRecord+
- 1
class ReposValidator < Dragnet::Validators::Validator
- 1
attr_reader :test_record, :repos, :path
# @param [Dragnet::TestRecord] test_record The Test Record to validate.
# @param [Pathname] path The path where the repositories are supposed to
# be located.
- 1
def initialize(test_record, path)
- 11
@test_record = test_record
- 11
@path = path
- 11
@repos = test_record.repos
end
# Validates the +Repo+ objects inside the given +TestCase+
- 1
def validate
- 11
return unless repos
- 10
validate_paths
end
- 1
private
# Validates the +paths+ of the +Repo+ objects (makes sure the paths
# exist). Knowing that these paths exist, the +files+ attribute of the
# +Repo+ object can be validated as well.
# @raise [Dragnet::Errors::RepoPathNotFoundError] If one or more of the
# paths cannot be found.
# @raise [Dragnet::Errors::FileNotFoundError] If any of the files listed
# in the +files+ attribute do not exist inside the given +path+.
- 1
def validate_paths
- 10
repos.each do |repo|
- 27
repo_path = repo.path = repo.path.gsub('\\', '/')
- 27
repo_path = Pathname.new(repo_path)
- 27
complete_path = repo_path.absolute? ? repo_path : path / repo_path
- 27
if complete_path.exist?
- 25
validate_files(repo, complete_path)
- 24
next
end
- 2
repo_path_not_found(repo_path)
end
end
# Validates the existence of the files listed in the +files+ attributes
# inside the +Repo+ object inside the +Repo+'s +path+.
# @param [Dragnet::Repo] repo The +Repo+ whose files should be validated.
# @param [Pathname] complete_path The path to the repository.
- 1
def validate_files(repo, complete_path)
- 25
return unless repo.files
- 6
Dragnet::Validators::FilesValidator.new(repo, complete_path).validate
end
# Raises a Dragnet::Errors::RepoPathNotFoundError with the appropriate
# message (which depends on whether the path is absolute or relative).
# @param [Pathname] repo_path The path that couldn't be found.
# @raise [Dragnet::Errors::RepoPathNotFoundError] is always raised.
- 1
def repo_path_not_found(repo_path)
- 2
message = "Cannot find the repository path #{repo_path}"
- 2
message += " inside #{path}" if repo_path.relative?
- 2
raise Dragnet::Errors::RepoPathNotFoundError, message
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../errors/yaml_format_error'
- 1
module Dragnet
- 1
module Validators
# Base class for all validators.
- 1
class Validator
- 1
private
# Raises a +Dragnet::Errors::YAMLFormatError+ with the given message.
# @param [String] message The message for the exception.
# @raise [Dragnet::Errors::YAMLFormatError] Is always raised with the
# given message.
- 1
def yaml_format_error(message)
- 2
raise Dragnet::Errors::YAMLFormatError, message
end
end
end
end
# frozen_string_literal: true
- 1
require 'colorize'
- 1
require_relative 'errors/missing_timestamp_attribute_error'
- 1
module Dragnet
# Holds the verification result of a Manual Test Record
- 1
class VerificationResult
- 1
VALID_STATUSES = %i[passed skipped failed].freeze
- 1
attr_reader :status, :reason, :started_at, :finished_at
# Creates a new instance of the class.
# @param [Symbol] status The status
# @param [String] reason
- 1
def initialize(status:, reason: nil)
- 147
self.status = status
- 146
@reason = reason
end
- 1
def passed?
- 33
status == :passed
end
- 1
def skipped?
- 5
status == :skipped
end
- 1
def failed?
- 2
status == :failed
end
# Assigns the given status
# @param [Symbol] status The status
# @raise [ArgumentError] If the given status is not one of the accepted
# valid statuses.
- 1
def status=(status)
- 149
unless VALID_STATUSES.include?(status)
- 2
raise ArgumentError, "Invalid status #{status}."\
" Valid statuses are: #{VALID_STATUSES.join(', ')}"
end
- 147
@status = status
end
# Sets the verification's start time.
# @param [Time] time The verification's start time.
# @raise [ArgumentError] If the given +time+ is not an instance of +Time+.
# @raise [ArgumentError] If +finished_at+ is set and the given +time+ is
# bigger than or equal to it.
- 1
def started_at=(time)
- 83
validate_time(time)
- 82
raise ArgumentError, 'started_at must be smaller than finished_at' if finished_at && time >= finished_at
- 81
@runtime = nil
- 81
@started_at = time
end
# Sets the verification's finish time
# @param [Time] time The verification's finish time.
# @raise [TypeError] Is an attempt is made to set +finished_at+ before
# setting +started_at+.
# @raise [ArgumentError] If the given +time+ is not an instance of +Time+.
# @raise [ArgumentError] If +started_at+ is set and the given +time+ is
# smaller than or equal to it.
- 1
def finished_at=(time)
- 83
validate_time(time)
- 82
raise ArgumentError, 'finished_at must be greater than started_at' if started_at && time <= started_at
- 81
@runtime = nil
- 81
@finished_at = time
end
# @return [Float, nil] The runtime calculated from the started_at and
# finished_at attributes, if any of them is missing +nil+ is returned
# instead.
- 1
def runtime
- 75
runtime!
rescue Dragnet::Errors::MissingTimestampAttributeError
- 3
nil
end
# @return [Float] The runtime calculated from the started_at and finished_at
# timestamp attributes.
# @raise [TypeError] If either of these attributes is +nil+
- 1
def runtime!
- 79
@runtime ||= calculate_runtime
end
# @return [String] A string representation of the receiver that can be used
# to log the result of a verification.
- 1
def log_message
- 4
if passed?
- 1
'✔ PASSED '.colorize(:light_green)
- 3
elsif skipped?
- 1
"#{'âš SKIPPED'.colorize(:light_yellow)} #{reason}"
else
- 2
"#{'✘ FAILED '.colorize(:light_red)} #{reason || 'Unknown reason'}"
end
end
- 1
private
# Checks if the given object is a +Time+ and raises an +ArgumentError+ if it
# isn't.
# @param [Object] time The object to check.
# @raise [ArgumentError] If the given object is not a +Time+ object.
- 1
def validate_time(time)
- 166
raise ArgumentError, "Expected a Time object, got #{time.class}" unless time.is_a?(Time)
end
# @return [Float] The runtime calculated from the started_at and finished_at
# timestamp attributes.
# @raise [TypeError] If either of these attributes is +nil+
- 1
def calculate_runtime
- 79
if started_at.nil? || finished_at.nil?
- 6
raise Dragnet::Errors::MissingTimestampAttributeError,
'Both started_at and finished_at must be set in order to calculate the runtime'
end
- 73
finished_at - started_at
end
end
end
# frozen_string_literal: true
- 1
require 'git'
- 1
require_relative 'verifiers/test_record_verifier'
- 1
module Dragnet
# Executes the verification process on the given Test Records
- 1
class Verifier
- 1
attr_reader :test_records, :path, :logger
# Creates a new instance of the class.
# @param [Array<Hash>] test_records An array with the test records.
# @param [Dragnet::Repository] repository The repository where the MTR and
# the source files are stored.
# @param [#info] logger The logger object to use for output.
- 1
def initialize(test_records:, repository:, logger:)
- 25
@test_records = test_records
- 25
@repository = repository
- 25
@logger = logger
end
# Runs the verify process
# After the execution of this method each Test Record will get a +:result+
# key with the result of the verification process. This key contains a hash
# like the following:
#
# result: {
# status: :passed, # Either :passed, :failed or :skipped
# reason: 'String' # The reason for the failure (for :failed and :skipped)
# }
- 1
def verify
- 25
logger.info 'Verifying MTR files...'
- 25
test_records.each do |test_record|
- 75
logger.info "Verifying #{test_record.source_file}"
- 75
verify_mtr(test_record)
end
end
- 1
private
- 1
attr_reader :repository
# Verifies the given Manual Test Record
# Runs the given test record through all the verifiers. If no verifier adds
# a +result+ key to the Test Record then the method adds one with passed
# status.
# @param [Dragnet::TestRecord] test_record The Test Record to verify.
- 1
def verify_mtr(test_record)
- 75
started_at = Time.now.utc
- 75
verification_result = Dragnet::Verifiers::TestRecordVerifier.new(
test_record: test_record, repository: repository, test_records: test_records
).verify
- 75
finished_at = Time.now.utc
- 75
verification_result.started_at = started_at
- 75
verification_result.finished_at = finished_at
- 75
test_record.verification_result = verification_result
- 75
logger.info(verification_result.log_message)
end
end
end
# frozen_string_literal: true
- 1
require_relative 'verifiers/repos_verifier'
- 1
module Dragnet
# Namespace for the Verifier classes.
- 1
module Verifiers; end
end
# frozen_string_literal: true
- 1
require_relative '../verification_result'
- 1
require_relative 'repository_verifier'
- 1
module Dragnet
- 1
module Verifiers
# Checks for changes in the repository since the creation of the MTR Record
- 1
class ChangesVerifier < Dragnet::Verifiers::RepositoryVerifier
- 1
attr_reader :test_records
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# verify.
# @param [Dragnet::Repository] repository A +Dragnet::Repository+ object
# linked to the repository where the verification should be executed.
# @param [Array<Hash>] test_records The hash of all the test records. This
# is used to determine if the changes in the repository are only in the
# Test Record Files, in which case the Test Records will still be
# considered valid.
- 1
def initialize(test_record:, repository:, test_records:)
- 4
super(test_record: test_record, repository: repository)
- 4
@test_records = test_records
end
# Runs the verification process. Checks the changes on the repository
# between the Commit with the SHA1 registered in the MTR and the current
# HEAD.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ with
# the details of the changes found in the repository or +nil+ if no
# changes were found.
- 1
def verify
- 4
diff = repository.diff(sha1, 'HEAD')
- 4
return unless diff.size.positive?
- 3
find_changes(diff)
end
- 1
private
# Scans the given diff for changes. If changes are detected then a
# +:result+ key will be added to the +test_record+. Changes to the MTR
# files themselves are ignored.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ with
# the details of the changes found in the repository, or +nil+ if no
# changes were found.
- 1
def find_changes(diff)
- 3
diff.stats[:files].each do |file, _changes|
- 5
next if mtr_files.include?(file) # Changes to MTR files are ignored.
- 2
return Dragnet::VerificationResult.new(
status: :skipped,
reason: "Changes detected in the repository: #{shorten_sha1(sha1)}..#{shorten_sha1(repository.head.sha)}"\
" # -- #{file}"
)
end
nil
end
# @return [Array<Strings>] An array of strings with the paths to all the
# known MTR files. These will be excluded when checking from changes in
# the repository.
- 1
def mtr_files
- 5
@mtr_files ||= test_records.map do |test_record|
- 6
test_record.source_file.relative_path_from(path).to_s
end
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../verification_result'
- 1
require_relative 'repository_verifier'
- 1
module Dragnet
- 1
module Verifiers
# Checks if any of the files listed in the MTR have changed since the MTR
# was created.
- 1
class FilesVerifier < Dragnet::Verifiers::RepositoryVerifier
# Executes the verification process.
# Checks the changes in the repository. If a change in one of the files
# is detected a +:result+ key is added to the MTR, including the detected
# change.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# with the detected changes to the listed files or +nil+ if no changes
# are found.
- 1
def verify
- 4
changes = []
- 4
files.each do |file|
- 9
diff = repository.diff(sha1, 'HEAD').path(file.to_s)
- 9
next unless diff.size.positive?
- 4
changes << file
end
- 4
result_from(changes) if changes.any?
end
- 1
private
# @return [Array<String>] The paths to the files listed in the MTR file.
- 1
def files
- 4
@files ||= test_record.files.map do |file|
- 9
file.relative_path_from(path)
end
end
# Stores the detected changes on the Test Record
# @param [Array<String>] changes The array of changed files.
- 1
def result_from(changes)
- 2
Dragnet::VerificationResult.new(
status: :skipped,
reason: "Changes detected in listed file(s): #{shorten_sha1(sha1)}..#{shorten_sha1(repository.head.sha)}"\
" -- #{changes.join(' ')}"
)
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../errors/not_a_repository_error'
- 1
require_relative '../test_record'
- 1
require_relative 'changes_verifier'
- 1
require_relative 'files_verifier'
- 1
require_relative 'verifier'
- 1
module Dragnet
- 1
module Verifiers
# Verifies the +Repo+ objects attached to a +TestRecord+
- 1
class ReposVerifier < Dragnet::Verifiers::Verifier
- 1
attr_reader :multi_repository
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# verify.
# @param [Dragnet::MultiRepository] multi_repository The +MultiRepository+
# object that is supposed to contain the actual repositories inside.
- 1
def initialize(test_record:, multi_repository:)
- 16
super(test_record: test_record)
- 16
@multi_repository = multi_repository
end
# Carries out the verification of the +Repo+ objects.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# when the verification of any of the listed repositories fails and
# +nil+ when all of them pass the verification.
- 1
def verify
- 16
return unless test_record.repos&.any?
- 14
test_record.repos.each do |repo|
- 21
repository = fetch_repository(repo.path, multi_repository)
- 18
verification_result = verify_repo(repo, repository)
- 18
return verification_result if verification_result
end
rescue Dragnet::Errors::NotARepositoryError => e
- 3
Dragnet::VerificationResult.new(status: :failed, reason: e.message)
end
- 1
private
# Verifies the given +Repo+ object using the given +Dragnet::Repository+
# object and a Proxy TestRecord object.
# @param [Dragnet::Repo] repo +Repo+ the +Repo+ object to verify.
# @param [Dragnet::Repository] repository The +Repository+ object that
# actually contains the source the +Repo+ object is referring to.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# when the verification fails or +nil+ when the verification passes.
- 1
def verify_repo(repo, repository)
# The Proxy TestRecord object allows the use of the +FilesVerifier+ and
# the +ChangesVerifier+ since they expect a +TestRecord+ and not a
# +Repo+ object.
- 18
proxy_test_record = Dragnet::TestRecord.new(files: repo.files, sha1: repo.sha1)
- 18
if repo.files
- 11
Dragnet::Verifiers::FilesVerifier
.new(test_record: proxy_test_record, repository: repository).verify
else
- 7
Dragnet::Verifiers::ChangesVerifier
.new(test_record: proxy_test_record, repository: repository, test_records: []).verify
end
end
# Fetches the +Repository+ object associated with the given path from the
# given +MultiRepository+ object.
# @param [Pathname] path The path of the repository.
# @param [Dragnet::MultiRepository] multi_repository The +MultiRepository+
# object that contains the repository to be fetched.
# @return [Dragnet::Repository] The +Repository+ object associated with
# the given path.
- 1
def fetch_repository(path, multi_repository)
- 21
multi_repository.repositories[path] ||= create_repository(multi_repository, path)
end
# Creates a new repository with the given path.
# @param [Dragnet::MultiRepository] multi_repository The +MultiRepository+
# object in which the individual repository should be created.
# @param [Pathname] path The path for the +Repository+ object.
# @return [Dragnet::Repository] The resulting +Repository+ object.
# @raise [Dragnet::Errors::NotARepositoryError] If the given path doesn't
# lead to a valid git repository or the repository cannot be opened.
- 1
def create_repository(multi_repository, path)
- 20
repository_path = multi_repository.path / path
- 20
Dragnet::Repository.new(path: repository_path)
rescue ArgumentError
- 3
raise Dragnet::Errors::NotARepositoryError,
"The path '#{path}' does not contain a valid git repository."
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'verifier'
- 1
module Dragnet
- 1
module Verifiers
# Base class for the Verifiers that need access to the repository to perform
# the validation.
- 1
class RepositoryVerifier < Dragnet::Verifiers::Verifier
- 1
attr_reader :repository
# @param [Dragnet::Repository] repository A +Dragnet::Repository+ object
# linked to the repository where the sources and MTR files are located
- 1
def initialize(test_record:, repository:)
- 8
super(test_record: test_record)
- 8
@repository = repository
end
- 1
private
# @return [String] The path to the repository.
- 1
def path
- 15
@path ||= repository.path
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'verifier'
- 1
require_relative '../verification_result'
- 1
module Dragnet
- 1
module Verifiers
# Verifies the +result+ field on the given MTR record.
- 1
class ResultVerifier < Dragnet::Verifiers::Verifier
# Performs the verification. If the +result+ field contains the "failed"
# text then a +result+ key will be added to the Test Record explaining
# the reason for the failure.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# when the verification fails and +nil+ when the verification passes.
- 1
def verify
- 5
return if test_record.passed?
- 4
Dragnet::VerificationResult.new(
status: :failed,
reason: "'result' field has the status '#{result}'"
)
end
- 1
private
# @return [String] The value for the +result+ key on the MTR file.
- 1
def result
- 4
@result ||= test_record.result
end
end
end
end
# frozen_string_literal: true
- 1
require_relative 'changes_verifier'
- 1
require_relative 'files_verifier'
- 1
require_relative 'repos_verifier'
- 1
require_relative 'result_verifier'
- 1
require_relative 'verifier'
- 1
module Dragnet
- 1
module Verifiers
# Performs the verification process over a single TestRecord object.
- 1
class TestRecordVerifier < Dragnet::Verifiers::Verifier
- 1
attr_reader :repository, :test_records
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# verify.
# @param [Dragnet::BaseRepository] repository An object representing the
# repository the test record is referring to.
# @param [Array<Dragnet::TestRecord>] test_records An array with all the
# +TestRecord+ objects found in the +Repository+. These are needed when
# changes to the repository are being verified. Changes targeting the
# MTRs only are ignored by the verifiers.
- 1
def initialize(test_record:, repository:, test_records:)
- 28
super(test_record: test_record)
- 28
@repository = repository
- 28
@test_records = test_records
end
# Performs the verification and attaches the corresponding
# +VerificationResult+ object to the +TestRecord+ object.
# @return [Dragnet::VerificationResult] The result of the verification
# process executed over the given +test_record+.
- 1
def verify
- 28
verification_result = verify_result
- 28
verification_result ||= if test_record.files
- 7
verify_files
- 17
elsif test_record.repos
- 7
verify_repos
else
- 10
verify_changes
end
- 28
verification_result || Dragnet::VerificationResult.new(status: :passed)
end
- 1
private
# Verifies the MTR's +result+ attribute.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# when the verification fails and +nil+ when the verification passes.
- 1
def verify_result
- 28
Dragnet::Verifiers::ResultVerifier.new(test_record: test_record).verify
end
# Verifies the files listed in the MTR, if any.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# with the detected changes to the listed files or +nil+ if no changes
# are found.
- 1
def verify_files
- 7
Dragnet::Verifiers::FilesVerifier
.new(test_record: test_record, repository: repository).verify
end
# Verifies the repositories listed in the MTR, only applies when working
# with a {Dragnet::MultiRepository} repository.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ object
# when the verification of any of the listed repositories fails and
# +nil+ when all of them pass the verification.
- 1
def verify_repos
- 7
Dragnet::Verifiers::ReposVerifier.new(test_record: test_record, multi_repository: repository).verify
end
# Verifies the changes in the repository between the revision referenced
# in the MTR and the tip of the current branch.
# @return [Dragnet::VerificationResult, nil] A +VerificationResult+ with
# the details of the changes found in the repository or +nil+ if no
# changes were found.
- 1
def verify_changes
- 10
Dragnet::Verifiers::ChangesVerifier
.new(test_record: test_record, repository: repository, test_records: test_records).verify
end
end
end
end
# frozen_string_literal: true
- 1
require_relative '../helpers/repository_helper'
- 1
module Dragnet
- 1
module Verifiers
# Base class for all validators.
- 1
class Verifier
- 1
include Dragnet::Helpers::RepositoryHelper
- 1
attr_reader :test_record
# Creates a new instance of the class.
# @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
# verify.
- 1
def initialize(test_record:)
- 57
@test_record = test_record
end
# Needs to be implemented by the child classes. This method is called to
# perform the verification on the given +test_record+.
# @return [Dragnet::VerificationResult, nil] The method should return a
# +VerificationResult+ object if the verification fails or +nil+ if it
# passes.
- 1
def verify
raise NotImplementedError, "Please implement #{__method__} in #{self.class}"
end
- 1
private
# @return [String] The SHA1 stored in the MTR File.
- 1
def sha1
- 17
@sha1 ||= test_record.sha1
end
end
end
end