SusSourceSusConfig

class Config

Represents the configuration for running tests.

Definitions

PATH = "config/sus.rb"

The default path to the configuration file.

def self.path(root)

Find the configuration file path for the given root directory.

Signature

parameter root String

The root directory to search in.

returns String | Nil

The path to the configuration file if it exists.

Implementation

def self.path(root)
	path = ::File.join(root, PATH)
	
	if ::File.exist?(path)
		return path
	end
end

def self.load(root: Dir.pwd, arguments: ARGV)

Load configuration from the given root directory.

Signature

parameter root String

The root directory to load configuration from.

parameter arguments Array

Command line arguments to parse.

returns Config

A new Config instance.

Implementation

def self.load(root: Dir.pwd, arguments: ARGV)
	derived = Class.new(self)
	
	if path = self.path(root)
		config = Module.new
		config.module_eval(::File.read(path), path)
		derived.prepend(config)
	end
	
	options = {
		verbose: !!arguments.delete("--verbose")
	}
	
	return derived.new(root, arguments, **options)
end

def initialize(root, paths, verbose: false)

Initialize a new Config instance.

Signature

parameter root String

The root directory for the project.

parameter paths Array

Optional paths to specific test files.

parameter verbose Boolean

Whether to output verbose information.

Implementation

def initialize(root, paths, verbose: false)
	@root = root
	@paths = paths
	@verbose = verbose
	
	@clock = Clock.new
	
	self.add_default_load_paths
end

def add_load_path(path)

Add a directory to the load path.

Signature

parameter path String

The path to add.

Implementation

def add_load_path(path)
	path = ::File.expand_path(path, @root)
	
	if ::File.directory?(path)
		$LOAD_PATH.unshift(path)
	end
end

def add_default_load_paths

Add default load paths (lib and fixtures).

Implementation

def add_default_load_paths
	add_load_path("lib")
	add_load_path("fixtures")
end

attr :root

Signature

attribute String

The root directory for the project.

attr :paths

Signature

attribute Array

Optional paths to specific test files.

def verbose?

Signature

returns Boolean

Whether verbose output is enabled.

Implementation

def verbose?
	@verbose
end

def partial?

Signature

returns Boolean

Whether only a partial set of tests is being run.

Implementation

def partial?
	@paths.any?
end

def output

Signature

returns Output

The output handler to use.

Implementation

def output
	@output ||= Sus::Output.default
end

DEFAULT_TEST_PATTERN = "test/**/*.rb"

The default pattern for finding test files.

def test_paths

Signature

returns Array(String)

Paths to all test files matching the default pattern.

Implementation

def test_paths
	return Dir.glob(DEFAULT_TEST_PATTERN, base: @root)
end

def make_registry

Create a new registry instance.

Signature

returns Registry

A new Registry instance.

Implementation

def make_registry
	Sus::Registry.new(root: @root)
end

def load_registry(paths = @paths)

Load the test registry, optionally filtering by paths.

Signature

parameter paths Array | Nil

Optional paths to filter tests by.

returns Registry, Filter

The loaded registry, possibly wrapped in a Filter.

Implementation

def load_registry(paths = @paths)
	registry = make_registry
	
	if paths&.any?
		registry = Sus::Filter.new(registry)
		paths.each do |path|
			registry.load(path)
		end
	else
		test_paths.each do |path|
			registry.load(path)
		end
	end
	
	return registry
end

def registry

Signature

returns Registry

The test registry, loading it if necessary.

Implementation

def registry
	@registry ||= self.load_registry
end

def prepare_warnings!

Prepare Ruby warnings for deprecated features.

Implementation

def prepare_warnings!
	Warning[:deprecated] = true
end

def before_tests(assertions, output: self.output)

Called before tests are run.

Signature

parameter assertions Assertions

The assertions instance.

parameter output Output

The output handler.

Implementation

def before_tests(assertions, output: self.output)
	@clock.reset!
	@clock.start!
	
	prepare_warnings!
end

def after_tests(assertions, output: self.output)

Called after tests are run.

Signature

parameter assertions Assertions

The assertions instance.

parameter output Output

The output handler.

Implementation

def after_tests(assertions, output: self.output)
	@clock.stop!
	
	self.print_summary(output, assertions)
end

def print_summary(output, assertions)

Print a summary of test results.

Signature

parameter output Output

The output handler.

parameter assertions Assertions

The assertions instance.

Implementation

def print_summary(output, assertions)
	assertions.print(output)
	output.puts
	
	print_finished_statistics(output, assertions)
	
	unless assertions.count.zero?
		if !partial? and assertions.passed?
			print_test_feedback(output, assertions)
		end
		
		print_slow_tests(output, assertions)
	end
	
	print_failed_assertions(output, assertions)
end

def print_finished_statistics(output, assertions)

Print finished statistics.

Signature

parameter output Output

The output handler.

parameter assertions Assertions

The assertions instance.

Implementation

def print_finished_statistics(output, assertions)
	duration = @clock.duration
	
	if assertions.count.zero?
		output.puts "🏴 Finished in ", @clock, "."
	else
		rate = assertions.count / duration
		output.puts "🏁 Finished in ", @clock, "; #{rate.round(3)} assertions per second."
	end
end

def print_test_feedback(output, assertions = nil, duration: @clock.duration, count: assertions.count, total: assertions.total)

Print feedback about the test suite.

Signature

parameter output Output

The output handler.

parameter assertions Assertions | Nil

The assertions instance.

parameter duration Float

The total duration of the test suite.

parameter count Integer

The number of assertions.

parameter total Integer

The number of tests.

Implementation

def print_test_feedback(output, assertions = nil,
	duration: @clock.duration,
	count: assertions.count,
	total: assertions.total
)
	rate = count / duration
	
	if total < 10 or count < 10
		output.puts "😭 You should write more tests and assertions!"
		
		# Statistics will be less meaningful with such a small amount of data, so give up:
		return
	end
	
	# Check whether there is at least, on average, one assertion (or more) per test:
	assertions_per_test = count / total
	if assertions_per_test < 1.0
		output.puts "😩 Your tests don't have enough assertions (#{assertions_per_test.round(1)} < 1.0)!"
	end
	
	# Give some feedback about the number of tests:
	if total < 20
		output.puts "🥲 You should write more tests (#{total}/20)!"
	elsif total < 50
		output.puts "🙂 Your test suite is starting to shape up, keep on at it (#{total}/50)!"
	elsif total < 100
		output.puts "😀 Your test suite is maturing, keep on at it (#{total}/100)!"
	else
		output.puts "🤩 Your test suite is amazing!"
	end
	
	# Give some feedback about the performance of the tests:
	if rate < 10.0
		output.puts "💔 Ouch! Your test suite performance is painful (#{rate.round(1)} < 10)!"
	elsif rate < 100.0
		output.puts "💩 Oops! Your test suite performance could be better (#{rate.round(1)} < 100)!"
	elsif rate < 1_000.0
		output.puts "💪 Good job! Your test suite has good performance (#{rate.round(1)} < 1000)!"
	elsif rate < 10_000.0
		output.puts "🎉 Great job! Your test suite has excellent performance (#{rate.round(1)} < 10000)!"
	else
		output.puts "🔥 Wow! Your test suite has outstanding performance (#{rate.round(1)} >= 10000.0)!"
	end
end

def print_slow_tests(output, assertions, threshold = 0.1)

Print information about slow tests.

Signature

parameter output Output

The output handler.

parameter assertions Assertions

The assertions instance.

parameter threshold Float

The threshold in seconds for considering a test slow.

Implementation

def print_slow_tests(output, assertions, threshold = 0.1)
	slowest_tests = assertions.passed.select{|test| test.clock > threshold}.sort_by(&:clock).reverse!
	
	if slowest_tests.empty?
		output.puts "🐇 No slow tests found! Well done!"
	else
		output.puts "🐢 Slow tests:"
		
		slowest_tests.each do |test|
			output.puts "\t", :variable, test.clock, :reset, ": ", test.target
		end
	end
end

def print_assertions(output, title, assertions)

Print a list of assertions.

Signature

parameter output Output

The output handler.

parameter title String

The title to print.

parameter assertions Array

The assertions to print.

Implementation

def print_assertions(output, title, assertions)
	if assertions.any?
		output.puts
		output.puts title
		
		assertions.each do |assertion|
			output.append(assertion.output)
		end
	end
end

def print_failed_assertions(output, assertions)

Print failed and errored assertions.

Signature

parameter output Output

The output handler.

parameter assertions Assertions

The assertions instance.

Implementation

def print_failed_assertions(output, assertions)
	print_assertions(output, "🤔 Failed assertions:", assertions.failed)
	print_assertions(output, "🔥 Errored assertions:", assertions.errored)
end