diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a58eae..630d59b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,23 +10,25 @@ cache: before_script: - crystal -v # Print out Crystal version for debugging - - shards spec: script: - - crystal spec --error-on-warnings + - shards + - crystal spec --error-on-warnings style: script: - - bin/ameba - - crystal tool format --check + - shards + - bin/ameba + - crystal tool format --check nightly: image: "crystallang/crystal:nightly" allow_failure: true script: - - crystal spec --error-on-warnings - - crystal tool format --check + - shards --ignore-crystal-version + - crystal spec --error-on-warnings + - crystal tool format --check pages: stage: deploy diff --git a/shard.yml b/shard.yml index d0038ce..a6ba14f 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: spectator -version: 0.9.20 +version: 0.9.22 description: | A feature-rich spec testing framework for Crystal with similarities to RSpec. @@ -13,4 +13,4 @@ license: MIT development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 0.12.0 + version: ~> 0.13.1 diff --git a/src/spectator.cr b/src/spectator.cr index 9c4907c..77c333e 100644 --- a/src/spectator.cr +++ b/src/spectator.cr @@ -6,7 +6,7 @@ module Spectator extend self # Current version of the Spectator library. - VERSION = "0.9.20" + VERSION = "0.9.22" # Top-level describe method. # All specs in a file must be wrapped in this call. @@ -45,7 +45,7 @@ module Spectator end end - # ditto + # :ditto: macro context(description, &block) describe({{description}}) {{block}} end diff --git a/src/spectator/command_line_arguments_config_source.cr b/src/spectator/command_line_arguments_config_source.cr index 7b31e99..ce3c7e5 100644 --- a/src/spectator/command_line_arguments_config_source.cr +++ b/src/spectator/command_line_arguments_config_source.cr @@ -61,7 +61,7 @@ module Spectator private def seed_option(parser, builder) parser.on("--seed INTEGER", "Set the seed for the random number generator (implies -r)") do |seed| builder.randomize - builder.seed = seed.to_i + builder.seed = seed.to_u64 end end @@ -74,7 +74,7 @@ module Spectator when /^rand/ builder.randomize parts = method.split(':', 2) - builder.seed = parts[1].to_i if parts.size > 1 + builder.seed = parts[1].to_u64 if parts.size > 1 else nil end diff --git a/src/spectator/config.cr b/src/spectator/config.cr index b7a1eda..a44564b 100644 --- a/src/spectator/config.cr +++ b/src/spectator/config.cr @@ -19,6 +19,9 @@ module Spectator # Indicates whether tests are run in a random order. getter? randomize : Bool + # Random seed used for number generation. + getter! random_seed : UInt64? + # Indicates whether profiling information should be displayed. getter? profile : Bool @@ -33,6 +36,7 @@ module Spectator @dry_run = builder.dry_run? @random = builder.random @randomize = builder.randomize? + @random_seed = builder.seed? @profile = builder.profile? @example_filter = builder.example_filter end diff --git a/src/spectator/config_builder.cr b/src/spectator/config_builder.cr index c650945..d65ffc1 100644 --- a/src/spectator/config_builder.cr +++ b/src/spectator/config_builder.cr @@ -11,6 +11,11 @@ module Spectator # Random number generator to use. protected getter random = Random::DEFAULT + def initialize + @seed = seed = @random.rand(UInt16).to_u64 + @random.new_seed(seed) + end + @primary_formatter : Formatting::Formatter? @additional_formatters = [] of Formatting::Formatter @fail_fast = false @@ -90,8 +95,12 @@ module Spectator @dry_run end + # Seed used for random number generation. + getter! seed : UInt64? + # Sets the seed for the random number generator. def seed=(seed) + @seed = seed @random = Random.new(seed) end diff --git a/src/spectator/dsl/assertions.cr b/src/spectator/dsl/assertions.cr index aa94b13..11b5ffe 100644 --- a/src/spectator/dsl/assertions.cr +++ b/src/spectator/dsl/assertions.cr @@ -207,7 +207,7 @@ module Spectator raise ExampleFailed.new(reason) end - # ditto + # :ditto: @[AlwaysInline] def fail fail("Example failed") diff --git a/src/spectator/dsl/matchers.cr b/src/spectator/dsl/matchers.cr index 3a2a703..a9f1030 100644 --- a/src/spectator/dsl/matchers.cr +++ b/src/spectator/dsl/matchers.cr @@ -450,8 +450,13 @@ module Spectator # expect(%i[a b c]).to contain(:a, :b) # ``` macro contain(*expected) - %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) - ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% if expected.id.starts_with?("{*") %} + %test_value = ::Spectator::TestValue.new({{expected.id[2...-1]}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% else %} + %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% end %} end # Indicates that some range (or collection) should contain another value. @@ -471,8 +476,13 @@ module Spectator # expect(..100).to contain(0, 50) # ``` macro cover(*expected) - %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) - ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% if expected.id.starts_with?("{*") %} + %test_value = ::Spectator::TestValue.new({{expected.id[2...-1]}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% else %} + %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::ContainMatcher.new(%test_value) + {% end %} end # Indicates that some value or set should contain another value. @@ -501,8 +511,13 @@ module Spectator # expect(%w[FOO BAR BAZ]).to have(/foo/i, String) # ``` macro have(*expected) - %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) - ::Spectator::Matchers::HaveMatcher.new(%test_value) + {% if expected.id.starts_with?("{*") %} + %test_value = ::Spectator::TestValue.new({{expected.id[2...-1]}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::HaveMatcher.new(%test_value) + {% else %} + %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}}) + ::Spectator::Matchers::HaveMatcher.new(%test_value) + {% end %} end # Indicates that some set, such as a `Hash`, has a given key. @@ -518,7 +533,7 @@ module Spectator ::Spectator::Matchers::HaveKeyMatcher.new(%test_value) end - # ditto + # :ditto: macro has_key(expected) have_key({{expected}}) end @@ -536,7 +551,7 @@ module Spectator ::Spectator::Matchers::HaveValueMatcher.new(%test_value) end - # ditto + # :ditto: macro has_value(expected) have_value({{expected}}) end @@ -548,8 +563,13 @@ module Spectator # expect([1, 2, 3]).to contain_exactly(3, 2, 1) # ``` macro contain_exactly(*expected) - %test_value = ::Spectator::TestValue.new(({{expected}}).to_a, {{expected.stringify}}) - ::Spectator::Matchers::ArrayMatcher.new(%test_value) + {% if expected.id.starts_with?("{*") %} + %test_value = ::Spectator::TestValue.new(({{expected.id[2...-1]}}).to_a, {{expected.stringify}}) + ::Spectator::Matchers::ArrayMatcher.new(%test_value) + {% else %} + %test_value = ::Spectator::TestValue.new(({{expected}}).to_a, {{expected.stringify}}) + ::Spectator::Matchers::ArrayMatcher.new(%test_value) + {% end %} end # Indicates that some set should contain the same values in any order as another set. @@ -597,8 +617,13 @@ module Spectator # expect(%i[a b c]).to have_attributes(size: 1..5, first: Symbol) # ``` macro have_attributes(**expected) - %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.double_splat.stringify}}) - ::Spectator::Matchers::AttributesMatcher.new(%test_value) + {% if expected.id.starts_with?("{**") %} + %test_value = ::Spectator::TestValue.new({{expected.id[3...-1]}}, {{expected.double_splat.stringify}}) + ::Spectator::Matchers::AttributesMatcher.new(%test_value) + {% else %} + %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.double_splat.stringify}}) + ::Spectator::Matchers::AttributesMatcher.new(%test_value) + {% end %} end # Verifies that all elements of a collection satisfy some matcher. diff --git a/src/spectator/expectations/expectation_partial.cr b/src/spectator/expectations/expectation_partial.cr index 6a38301..c837c86 100644 --- a/src/spectator/expectations/expectation_partial.cr +++ b/src/spectator/expectations/expectation_partial.cr @@ -52,7 +52,7 @@ module Spectator::Expectations stubs.each { |stub| to_not(stub) } end - # ditto + # :ditto: @[AlwaysInline] def not_to(matcher) : Nil to_not(matcher) @@ -86,7 +86,7 @@ module Spectator::Expectations to_not(stub) end - # ditto + # :ditto: @[AlwaysInline] def never_to(matcher) : Nil to_never(matcher) diff --git a/src/spectator/formatting/random_seed_text.cr b/src/spectator/formatting/random_seed_text.cr new file mode 100644 index 0000000..998fed1 --- /dev/null +++ b/src/spectator/formatting/random_seed_text.cr @@ -0,0 +1,14 @@ +module Spectator::Formatting + # Text displayed when using a random seed. + private struct RandomSeedText + # Creates the text object. + def initialize(@seed : UInt64) + end + + # Appends the command to the output. + def to_s(io) + io << "Randomized with seed " + io << @seed + end + end +end diff --git a/src/spectator/formatting/suite_summary.cr b/src/spectator/formatting/suite_summary.cr index 3d2dcfd..183a78e 100644 --- a/src/spectator/formatting/suite_summary.cr +++ b/src/spectator/formatting/suite_summary.cr @@ -49,6 +49,10 @@ module Spectator::Formatting private def stats(report) @io.puts Runtime.new(report.runtime) @io.puts StatsCounter.new(report).color + if (seed = report.random_seed?) + @io.puts + @io.puts RandomSeedText.new(seed) + end end # Produces the skipped tests text if fail-fast is enabled and tests were omitted. diff --git a/src/spectator/report.cr b/src/spectator/report.cr index 54cbb95..9ce1ebd 100644 --- a/src/spectator/report.cr +++ b/src/spectator/report.cr @@ -25,12 +25,16 @@ module Spectator # This will be greater than zero only in fail-fast mode. getter remaining_count + # Random seed used to determine test ordering. + getter! random_seed : UInt64? + # Creates the report. # The *results* are from running the examples in the test suite. # The *runtime* is the total time it took to execute the suite. # The *remaining_count* is the number of tests skipped due to fail-fast. # The *fail_blank* flag indicates whether it is a failure if there were no tests run. - def initialize(@results : Array(Result), @runtime, @remaining_count = 0, @fail_blank = false) + # The *random_seed* is the seed used for random number generation. + def initialize(@results : Array(Result), @runtime, @remaining_count = 0, @fail_blank = false, @random_seed = nil) @results.each do |result| case result when SuccessfulResult diff --git a/src/spectator/runner.cr b/src/spectator/runner.cr index 8bde725..d5c50f9 100644 --- a/src/spectator/runner.cr +++ b/src/spectator/runner.cr @@ -25,7 +25,8 @@ module Spectator # Generate a report and pass it along to the formatter. remaining = @suite.size - results.size - report = Report.new(results, elapsed, remaining, @config.fail_blank?) + seed = (@config.random_seed? if @config.randomize?) + report = Report.new(results, elapsed, remaining, @config.fail_blank?, seed) @config.each_formatter(&.end_suite(report, profile(report))) !report.failed?