require 'pathname'
require_relative 'util'
# This represents a specific test case.
class SassSpec::TestCase
attr_reader :metadata, :dir, :impl
# The path to the input file for this test case.
#
# Note that this file is not guaranteed to exist on disk, since this test case
# may be loaded from an HRX file.
attr_reader :input_path
# The Sass or SCSS input for this test case.
attr_reader :input
# The normalized expected CSS output. This is `nil` if the test is expected to
# fail, or if it's malformed and has no expectations.
attr_reader :expected
# The normalized expected warning text. This is `nil` if the test is expected
# to fail, or if it's malformed and has no expectations. It's `""` if the test
# is expected to succeed without warnings.
attr_reader :expected_warning
# The normalized expected error message. This is `nil` if the test is expected
# to succeed, or if it's malformed and has no expectations.
attr_reader :expected_error
# Returns the test case for the given SassSpec::Directory and implementation
# name.
def initialize(dir, impl)
@dir = dir
@impl = impl
@metadata = SassSpec::TestCaseMetadata.new(dir)
@result = false
input_files = @dir.glob("input.*")
if input_files.empty?
raise ArgumentError.new("No input file found in #{@dir}")
elsif input_files.size > 1
raise ArgumentError.new("Multiple input files found in #{@dir}")
end
@input = @dir.read(input_files.first)
# If there's an impl-specific output file, then this is a success test.
# Otherwise, it's an error test if there's *any* error file, impl-specific
# or otherwise.
if !file?("output.css", impl: true) && file?("error", impl: :auto)
@expected_error = SassSpec::Util.normalize_error(read("error", impl: :auto))
else
if file?("output.css", impl: :auto)
output = read("output.css", impl: :auto)
# we seem to get CP850 otherwise
# this provokes equal test to fail
output = String.new(output).force_encoding('ASCII-8BIT')
@expected = SassSpec::Util.normalize_output(output)
end
@expected_warning =
if file?("warning", impl: :auto)
SassSpec::Util.normalize_error(read("warning", impl: :auto))
else
""
end
end
end
def result?
@result
end
def finalize(result)
@result = result
end
def name
@metadata.name
end
def should_fail?
!!expected_error
end
def todo?
@metadata.todo?(impl)
end
def warning_todo?
@metadata.warning_todo?(impl)
end
def ignore?
@metadata.ignore_for?(impl)
end
def ignore_warning?
@metadata.ignore_warning_for?(impl)
end
# Returns whether a file exists at `path` within this test case's directory.
#
# If `impl` is `true`, this adds an implementation-specific suffix to the
# path. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it returns true if either the implementation-specific file or the base file
# exists.
def file?(path, impl: false)
path = _path(path, impl)
path && @dir.file?(path)
end
# Reads the file at `path` within this test case's directory.
#
# If `impl` is `true`, this adds an implementation-specific suffix to the
# path. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it returns the contents of implementation-specific file if it exists, or the
# base file if *it* exists, or throws an error if neither exists.
def read(path, impl: false)
unless resolved_path = _path(path, impl)
raise "#@dir/#{path} doesn't exist"
end
@dir.read(resolved_path)
end
# Writes `contents` to `path` within this test case's directory.
#
# If `impl` is `true`, this adds an implementation-specific suffix to the
# path. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it writes to the implementation-specific file if it exists, and the base
# file otherwise.
def write(path, contents, impl: false)
@dir.write(_path(path, impl) || path, contents)
end
# Deletes the file at `path` within this test case's directory.
#
# If `if_exists` is `true`, don't throw an error if the file doesn't exist.
#
# If `impl` is `true`, this adds an implementation-specific suffix to the
# path. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it deletes the implementation-specific file if it exists, or the base file
# if *it* exists, or throws an error if neither exists.
def delete(path, if_exists: false, impl: false)
unless resolved_path = _path(path, impl)
return if if_exists
raise "#@dir/#{path} doesn't exist"
end
@dir.delete(resolved_path, if_exists: if_exists)
end
# Renames the file at `old` to `new` within this test case's directory.
#
# If `impl` is `true`, this adds an implementation-specific suffix to both
# paths. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it renames the implementation-specific file if it exists to an
# implementation-specific version of `new`, or the base file if *it* exists to
# `new`, or throws an error if neither exist.
def rename(old, new, impl: false)
unless resolved_old = _path(old, impl)
raise "#@dir/#{old} doesn't exist"
end
new = _path(new, true) if resolved_old.include?("-#{@impl}")
@dir.rename(resolved_old, new)
end
# Invokes EngineAdapter#compile on this test case and returns the result.
def run(engine_adapter)
@dir.with_real_files do
engine_adapter.compile(
File.join(@dir.path, @dir.glob("input.*").first),
@metadata.precision || 10)
end
end
private
# Returns the path of `path` within `dir`.
#
# If `impl` is `true`, this adds an implementation-specific suffix to the
# path. If `impl` is false (the default), it does not. If `impl` is `:auto`,
# it returns the implementation-specific file if it exists, or the base file
# if *it* exists, or `nil` if neither exists.
def _path(path, impl)
return path unless impl
extension = File.extname(path)
path_without_extension = extension.empty? ? path : path[0...-extension.length]
path_with_impl = "#{path_without_extension}-#{@impl}#{extension}"
return path_with_impl if impl == true || @dir.file?(path_with_impl)
return path if @dir.file?(path)
end
end