From 3eed97d6d5e529f110712adf171657aaa47fc33d Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Sun, 27 Oct 2019 12:13:31 +0100
Subject: [PATCH 1/9] Bump to crystal 0.31.1

---
 .circleci/config.yml | 2 +-
 shard.yml            | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6a13a208..3565b1eb 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@ version: 2
 jobs:
   test-crystal: &test-template
     docker:
-      - image: crystallang/crystal:0.29.0
+      - image: crystallang/crystal:0.31.1
     steps:
       - checkout
   test:
diff --git a/shard.yml b/shard.yml
index 7c9c8c4a..f36b36b8 100644
--- a/shard.yml
+++ b/shard.yml
@@ -4,7 +4,7 @@ version: 6.0.0
 authors:
   - Hannes Käufler <hannes.kaeufler@gmail.com>
 
-crystal: 0.29.0
+crystal: 0.31.1
 
 license: MIT
 

From 773f298cbbcb3767f079fc623a85b64fb2af3fd8 Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Sun, 27 Oct 2019 12:16:46 +0100
Subject: [PATCH 2/9] Run formatter

---
 spec/integration_spec.cr | 146 +++++++++++++++++++--------------------
 1 file changed, 73 insertions(+), 73 deletions(-)

diff --git a/spec/integration_spec.cr b/spec/integration_spec.cr
index 08a03e5e..301bcc05 100644
--- a/spec/integration_spec.cr
+++ b/spec/integration_spec.cr
@@ -1,96 +1,96 @@
 require "./spec_helper"
 
 {% unless flag?("skip-integration") %}
-describe Crytic do
-  describe "--help/-h" do
-    it "prints usage info" do
-      result = run_crytic("--help")
-      result.output.should contain("Usage: crytic [arguments]")
-      result.exit_code.should eq 0
-      result = run_crytic("-h")
-      result.output.should contain("Usage: crytic [arguments]")
-      result.exit_code.should eq 0
+  describe Crytic do
+    describe "--help/-h" do
+      it "prints usage info" do
+        result = run_crytic("--help")
+        result.output.should contain("Usage: crytic [arguments]")
+        result.exit_code.should eq 0
+        result = run_crytic("-h")
+        result.output.should contain("Usage: crytic [arguments]")
+        result.exit_code.should eq 0
+      end
     end
-  end
 
-  describe "--preamble/-p" do
-    it "injects the given custom preamble, failing the neutral mutant" do
-      result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr -p 'exit 1'")
-      result.output.should contain("unmodified subject")
-      result.output.should_not contain("ConditionFlip")
-      result.exit_code.should eq 1
+    describe "--preamble/-p" do
+      it "injects the given custom preamble, failing the neutral mutant" do
+        result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr -p 'exit 1'")
+        result.output.should contain("unmodified subject")
+        result.output.should_not contain("ConditionFlip")
+        result.exit_code.should eq 1
+      end
     end
-  end
 
-  describe "with a fully covered subject" do
-    it "passes the mutation specs" do
-      result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/fully_covered_spec.cr")
-      result.output.should contain("✅ ConditionFlip")
-      result.output.should contain("✅ BoolLiteralFlip")
-      result.output.should contain("3 covered")
-      result.exit_code.should eq 0
+    describe "with a fully covered subject" do
+      it "passes the mutation specs" do
+        result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/fully_covered_spec.cr")
+        result.output.should contain("✅ ConditionFlip")
+        result.output.should contain("✅ BoolLiteralFlip")
+        result.output.should contain("3 covered")
+        result.exit_code.should eq 0
+      end
     end
-  end
 
-  describe "with an insufficiently covered subject" do
-    it "fails the mutation specs" do
-      result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr")
-      result.output.should contain("❌ ConditionFlip")
-      result.output.should contain("❌ BoolLiteralFlip")
-      result.output.should contain("3 uncovered")
-      result.exit_code.should be > 0
-    end
+    describe "with an insufficiently covered subject" do
+      it "fails the mutation specs" do
+        result = run_crytic("-s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr")
+        result.output.should contain("❌ ConditionFlip")
+        result.output.should contain("❌ BoolLiteralFlip")
+        result.output.should contain("3 uncovered")
+        result.exit_code.should be > 0
+      end
 
-    it "exits successfully when the msi threshold is set sufficiently" do
-      result = run_crytic("--min-msi=0.0 -s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr")
-      result.exit_code.should eq 0
+      it "exits successfully when the msi threshold is set sufficiently" do
+        result = run_crytic("--min-msi=0.0 -s ./fixtures/conditionals/fully_covered.cr ./fixtures/conditionals/uncovered_spec.cr")
+        result.exit_code.should eq 0
+      end
     end
-  end
 
-  describe "without passing a subject or tests" do
-    it "mutates all sources and runs all tests" do
-      result = run_crytic_in_dir("./fixtures/autofind")
-      result.output.should contain("✅ ConditionFlip")
-      result.output.should contain("✅ BoolLiteralFlip")
-      result.output.should contain("✅ NumberLiteralSignFlip")
-      result.output.should contain("✅ NumberLiteralChange")
-      result.output.should contain("❌ NumberLiteralSignFlip")
-      result.output.should contain("❌ NumberLiteralChange")
-      result.output.should contain("2 uncovered")
-      result.exit_code.should be > 0
+    describe "without passing a subject or tests" do
+      it "mutates all sources and runs all tests" do
+        result = run_crytic_in_dir("./fixtures/autofind")
+        result.output.should contain("✅ ConditionFlip")
+        result.output.should contain("✅ BoolLiteralFlip")
+        result.output.should contain("✅ NumberLiteralSignFlip")
+        result.output.should contain("✅ NumberLiteralChange")
+        result.output.should contain("❌ NumberLiteralSignFlip")
+        result.output.should contain("❌ NumberLiteralChange")
+        result.output.should contain("2 uncovered")
+        result.exit_code.should be > 0
+      end
     end
-  end
 
-  describe "subject without any coverage" do
-    it "fails all mutants" do
-      result = run_crytic("-s ./fixtures/uncovered/without.cr ./fixtures/uncovered/without_spec.cr")
-      result.output.should contain("❌ BoolLiteralFlip")
-      result.output.should contain("❌ ConditionFlip")
-      result.output.should contain("❌ NumberLiteralSignFlip")
-      result.output.should contain("❌ NumberLiteralChange")
-      result.output.should contain("9 uncovered")
-      result.exit_code.should be > 0
+    describe "subject without any coverage" do
+      it "fails all mutants" do
+        result = run_crytic("-s ./fixtures/uncovered/without.cr ./fixtures/uncovered/without_spec.cr")
+        result.output.should contain("❌ BoolLiteralFlip")
+        result.output.should contain("❌ ConditionFlip")
+        result.output.should contain("❌ NumberLiteralSignFlip")
+        result.output.should contain("❌ NumberLiteralChange")
+        result.output.should contain("9 uncovered")
+        result.exit_code.should be > 0
+      end
     end
-  end
 
-  describe "a failing initial test suite" do
-    it "reports initial failure" do
-      result = run_crytic("-s ./fixtures/uncovered/without.cr ./fixtures/failing/failing_spec.cr")
-      result.output.should contain "❌ Original test suite failed.\n"
-      result.output.should contain "no overload matches"
-      result.exit_code.should be > 0
+    describe "a failing initial test suite" do
+      it "reports initial failure" do
+        result = run_crytic("-s ./fixtures/uncovered/without.cr ./fixtures/failing/failing_spec.cr")
+        result.output.should contain "❌ Original test suite failed.\n"
+        result.output.should contain "no overload matches"
+        result.exit_code.should be > 0
+      end
     end
-  end
 
-  describe "a subject that is mutated into an endless loop" do
-    it "finishes and reports a timed out spec" do
-      result = run_crytic("-s ./fixtures/timeout/timeout.cr ./fixtures/timeout/timeout_spec.cr")
-      result.output.should contain "✅ Original test suite passed.\n"
-      result.output.should contain "1 timeout"
-      result.exit_code.should be > 0
+    describe "a subject that is mutated into an endless loop" do
+      it "finishes and reports a timed out spec" do
+        result = run_crytic("-s ./fixtures/timeout/timeout.cr ./fixtures/timeout/timeout_spec.cr")
+        result.output.should contain "✅ Original test suite passed.\n"
+        result.output.should contain "1 timeout"
+        result.exit_code.should be > 0
+      end
     end
   end
-end
 {% end %}
 
 def run_crytic_in_dir(dir : String)

From 134769b0fcf409376c5162654209999882e65180 Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Sun, 27 Oct 2019 12:25:35 +0100
Subject: [PATCH 3/9] Remove deprecation warnings by explicitly stating
 overloaded methods return types

---
 spec/fake_generator.cr                      | 2 +-
 spec/fake_mutation.cr                       | 2 +-
 src/crytic/generator/in_memory_generator.cr | 2 +-
 src/crytic/mutation/isolated_mutation.cr    | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/spec/fake_generator.cr b/spec/fake_generator.cr
index 89e842ef..696fd6f3 100644
--- a/spec/fake_generator.cr
+++ b/spec/fake_generator.cr
@@ -6,7 +6,7 @@ class FakeGenerator < Crytic::Generator::Generator
                  @neutral = FakeMutation.new.as(Crytic::Mutation::Mutation))
   end
 
-  def mutations_for(source : Array(Crytic::Subject), specs : Array(String))
+  def mutations_for(source : Array(Crytic::Subject), specs : Array(String)) : Array(Crytic::Generator::MutationSet)
     [Crytic::Generator::MutationSet.new(
       neutral: @neutral,
       mutated: @mutations)]
diff --git a/spec/fake_mutation.cr b/spec/fake_mutation.cr
index c9f4c763..8a986eb6 100644
--- a/spec/fake_mutation.cr
+++ b/spec/fake_mutation.cr
@@ -16,7 +16,7 @@ class FakeMutation < Crytic::Mutation::Mutation
   def initialize(@reported_status = Crytic::Mutation::Status::Uncovered)
   end
 
-  def run
+  def run : Crytic::Mutation::Result
     @run_call_count += 1
     Crytic::Mutation::Result.new(@reported_status, irrelevant_mutant, "", "")
   end
diff --git a/src/crytic/generator/in_memory_generator.cr b/src/crytic/generator/in_memory_generator.cr
index 768e468b..18539509 100644
--- a/src/crytic/generator/in_memory_generator.cr
+++ b/src/crytic/generator/in_memory_generator.cr
@@ -15,7 +15,7 @@ module Crytic::Generator
     )
     end
 
-    def mutations_for(sources : Array(Subject), specs : Array(String))
+    def mutations_for(sources : Array(Subject), specs : Array(String)) : Array(MutationSet)
       sources
         .map do |src|
           MutationSet.new(
diff --git a/src/crytic/mutation/isolated_mutation.cr b/src/crytic/mutation/isolated_mutation.cr
index 2cfb6bb7..004844b1 100644
--- a/src/crytic/mutation/isolated_mutation.cr
+++ b/src/crytic/mutation/isolated_mutation.cr
@@ -14,7 +14,7 @@ module Crytic::Mutation
 
     # Compiles the mutated source code into a binary and runs this binary,
     # recording exit code, stderr and stdout output.
-    def run
+    def run : Result
       mutated = @environment.perform_mutation
       process_result = run(mutated)
       success_messages_in_output = /Finished/ =~ process_result[:output]

From c4bcd6f0fb81838009f10a665094c01eeafafefb Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Sun, 27 Oct 2019 12:25:46 +0100
Subject: [PATCH 4/9] Avoid deprecated Time.now

---
 src/crytic/reporter/io_reporter.cr | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/crytic/reporter/io_reporter.cr b/src/crytic/reporter/io_reporter.cr
index dfcb9770..182c07ed 100644
--- a/src/crytic/reporter/io_reporter.cr
+++ b/src/crytic/reporter/io_reporter.cr
@@ -8,7 +8,7 @@ module Crytic::Reporter
   class IoReporter < Reporter
     INDENT = "    "
 
-    def initialize(@io : IO, @start_time = Time.now)
+    def initialize(@io : IO, @start_time = Time.utc)
     end
 
     def report_original_result(original_result)
@@ -77,7 +77,7 @@ module Crytic::Reporter
     end
 
     private def elapsed_time
-      Time.now - @start_time
+      Time.utc - @start_time
     end
   end
 end

From 968326578d0500419b5f237c2f9c1941dcd6a84b Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Fri, 1 Nov 2019 20:48:06 +0100
Subject: [PATCH 5/9] Ooof. Be more lax about the expected output.

We have a default preamble which enables
fail fast mode in specs. Between crystal 0.30.1
and 0.31.0 this changed the output. The new crystal
doesnt print a summary anymore, which means we will
not find "Finished" in it. Instead only one "F" is printed and
that's it (if there is exactly one test which fails).

See also https://github.com/crystal-lang/crystal/issues/8359
---
 src/crytic/mutation/isolated_mutation.cr | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/crytic/mutation/isolated_mutation.cr b/src/crytic/mutation/isolated_mutation.cr
index 004844b1..78f3f1d4 100644
--- a/src/crytic/mutation/isolated_mutation.cr
+++ b/src/crytic/mutation/isolated_mutation.cr
@@ -17,7 +17,7 @@ module Crytic::Mutation
     def run : Result
       mutated = @environment.perform_mutation
       process_result = run(mutated)
-      success_messages_in_output = /Finished/ =~ process_result[:output]
+      success_messages_in_output = /F/ =~ process_result[:output]
       status = if process_result[:exit_code] == ProcessRunner::SUCCESS
                  Status::Uncovered
                elsif process_result[:exit_code] == ProcessRunner::TIMEOUT

From bc4720ebac4c4d1715632584dbdc26bccbbe9291 Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Fri, 1 Nov 2019 22:32:41 +0100
Subject: [PATCH 6/9] Hah, even the exit code is wrong. Disable fail fast mode
 alltogether. There is another bug in crystal which makes the fail fast mode
 entirely unusable for crystal.

---
 spec/cli_options_spec.cr  |  8 ++++++++
 src/crytic/cli_options.cr | 21 ++++++++++++++++++++-
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/spec/cli_options_spec.cr b/spec/cli_options_spec.cr
index 863d57ff..d07b6ba1 100644
--- a/spec/cli_options_spec.cr
+++ b/spec/cli_options_spec.cr
@@ -98,11 +98,19 @@ module Crytic
       end
       {% end %}
 
+      {% if Crystal::VERSION == "0.31.0" || Crystal::VERSION == "0.31.1" %}
+      it "defaults to a simple spec preamble" do
+        opts = cli_options_parser.parse([] of String)
+
+        opts.preamble.should eq "require \"spec\"\n\n"
+      end
+      {% else %}
       it "defaults to a fail fast preamble" do
         opts = cli_options_parser.parse([] of String)
 
         opts.preamble.should eq Generator::Generator::DEFAULT_PREAMBLE
       end
+      {% end %}
 
       {% for flag in ["-m", "--min-msi"] %}
       it "accepts a msi threshold with {{ flag.id }}" do
diff --git a/src/crytic/cli_options.cr b/src/crytic/cli_options.cr
index d07fe82d..88370df7 100644
--- a/src/crytic/cli_options.cr
+++ b/src/crytic/cli_options.cr
@@ -2,6 +2,7 @@ require "./generator/generator"
 require "./mutant/possibilities"
 require "./reporter/*"
 require "./side_effects"
+require "colorize"
 require "option_parser"
 
 module Crytic
@@ -9,8 +10,8 @@ module Crytic
     DEFAULT_SPEC_FILES_GLOB = "./spec/**/*_spec.cr"
     getter msi_threshold = 100.0
     getter mutants : Array(Mutant::Possibilities) = Generator::Generator::ALL_MUTANTS
-    getter preamble = Generator::Generator::DEFAULT_PREAMBLE
     getter reporters = [] of Reporter::Reporter
+    @preamble = Generator::Generator::DEFAULT_PREAMBLE
     @spec_files = [] of String
     @subject = [] of String
 
@@ -87,6 +88,24 @@ module Crytic
       end.map { |path| Subject.from_filepath(path) }
     end
 
+    def preamble
+      {% if Crystal::VERSION == "0.31.1" || Crystal::VERSION == "0.31.0" %}
+        if @preamble =~ /fail_fast/
+          puts(<<-MSG.colorize(:yellow))
+
+          ⚠️  You specified a preamble that uses Spec#fail_fast. Due to a bug
+             in crystal 0.31.0 and 0.31.1, the Spec#fail_fast mode cannot be
+             used in crytic. We disabled it for you. See
+             https://github.com/crystal-lang/crystal/issues/8420 for details.
+
+          MSG
+          return @preamble.gsub(/Spec\.fail_fast\s*=\s*true/, "")
+        end
+      {% end %}
+
+      @preamble
+    end
+
     private def console_reporter
       Reporter::IoReporter.new(@side_effects.std_out)
     end

From 10073428493d07b17cfafae6ad6563b2c1d74569 Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Fri, 1 Nov 2019 22:36:37 +0100
Subject: [PATCH 7/9] Run tests on both crystal 0.30.1 and 0.31.1

---
 .circleci/config.yml     | 16 +++++++++++++---
 spec/cli_options_spec.cr | 16 ++++++++--------
 2 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3565b1eb..f5d4b34b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -6,7 +6,15 @@ jobs:
       - image: crystallang/crystal:0.31.1
     steps:
       - checkout
-  test:
+  test-crystal-0.30.1:
+    <<: *test-template
+    docker:
+      - image: crystallang/crystal:0.30.1
+    steps:
+      - checkout
+      - run: shards
+      - run: SPEC_VERBOSE=1 ./bin/test
+  test-crystal-0.31.1:
     <<: *test-template
     steps:
       - checkout
@@ -29,7 +37,8 @@ workflows:
   version: 2
   ci:
     jobs:
-      - test
+      - test-crystal-0.31.1
+      - test-crystal-0.30.1
   nightly:
     triggers:
       - schedule:
@@ -38,5 +47,6 @@ workflows:
             branches:
               only: master
     jobs:
-      - test
+      - test-crystal-0.31.1
+      - test-crystal-0.30.1
       - test-mutations
diff --git a/spec/cli_options_spec.cr b/spec/cli_options_spec.cr
index d07b6ba1..f1b46723 100644
--- a/spec/cli_options_spec.cr
+++ b/spec/cli_options_spec.cr
@@ -99,17 +99,17 @@ module Crytic
       {% end %}
 
       {% if Crystal::VERSION == "0.31.0" || Crystal::VERSION == "0.31.1" %}
-      it "defaults to a simple spec preamble" do
-        opts = cli_options_parser.parse([] of String)
+        it "defaults to a simple spec preamble" do
+          opts = cli_options_parser.parse([] of String)
 
-        opts.preamble.should eq "require \"spec\"\n\n"
-      end
+          opts.preamble.should eq "require \"spec\"\n\n"
+        end
       {% else %}
-      it "defaults to a fail fast preamble" do
-        opts = cli_options_parser.parse([] of String)
+        it "defaults to a fail fast preamble" do
+          opts = cli_options_parser.parse([] of String)
 
-        opts.preamble.should eq Generator::Generator::DEFAULT_PREAMBLE
-      end
+          opts.preamble.should eq Generator::Generator::DEFAULT_PREAMBLE
+        end
       {% end %}
 
       {% for flag in ["-m", "--min-msi"] %}

From 9f1d89e161845d43d313a2ea421f6c146f83cd0a Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Fri, 1 Nov 2019 22:42:20 +0100
Subject: [PATCH 8/9] Add changelog for 0.31.x compat

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bab29ce7..d915c2c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased] - Unknown
 
+- Crystal 0.31.x compatibility (no changes were needed for crystal 0.30.x). Be careful
+  with crystal 0.31.0 and 0.31.1 because there is a bug that might cause your CI job
+  to pass even with failing tests. See [#8420](https://github.com/crystal-lang/crystal/issues/8420).
+
 ## [6.0.0] - 2019-06-30
 
 ### Changed

From 6622ce7309889d4a8693a805ab0e5fd98b8851db Mon Sep 17 00:00:00 2001
From: Hannes Kaeufler <hannes.kaeufler@gmail.com>
Date: Fri, 1 Nov 2019 23:22:18 +0100
Subject: [PATCH 9/9] Simplify conditional spec

---
 spec/cli_options_spec.cr | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/spec/cli_options_spec.cr b/spec/cli_options_spec.cr
index f1b46723..cdd28153 100644
--- a/spec/cli_options_spec.cr
+++ b/spec/cli_options_spec.cr
@@ -98,19 +98,15 @@ module Crytic
       end
       {% end %}
 
-      {% if Crystal::VERSION == "0.31.0" || Crystal::VERSION == "0.31.1" %}
-        it "defaults to a simple spec preamble" do
-          opts = cli_options_parser.parse([] of String)
+      it "defaults to a fail fast preamble" do
+        opts = cli_options_parser.parse([] of String)
 
+        {% if Crystal::VERSION == "0.31.0" || Crystal::VERSION == "0.31.1" %}
           opts.preamble.should eq "require \"spec\"\n\n"
-        end
-      {% else %}
-        it "defaults to a fail fast preamble" do
-          opts = cli_options_parser.parse([] of String)
-
+        {% else %}
           opts.preamble.should eq Generator::Generator::DEFAULT_PREAMBLE
-        end
-      {% end %}
+        {% end %}
+      end
 
       {% for flag in ["-m", "--min-msi"] %}
       it "accepts a msi threshold with {{ flag.id }}" do