RSpec.describe Mutant::Reporter::CLI do let(:object) { described_class.new(output, format) } let(:output) { StringIO.new } let(:framed_format) do described_class::Format::Framed.new( tty: false, tput: described_class::Tput::UNAVAILABLE ) end let(:progressive_format) do described_class::Format::Progressive.new(tty: false) end let(:format) { framed_format } def contents output.rewind output.read end def self.it_reports(expected_content) it 'writes expected report to output' do subject expect(contents).to eql(strip_indent(expected_content)) end end before do allow(Time).to receive(:now).and_return(Time.now) end let(:result) do Mutant::Result::Env.new( env: env, runtime: 1.1, subject_results: subject_results ) end let(:env) do double( 'Env', class: Mutant::Env, matchable_scopes: matchable_scopes, config: config, subjects: subjects, mutations: subjects.flat_map(&:mutations) ) end let(:config) { Mutant::Config::DEFAULT.update(jobs: 1) } let(:mutation_class) { Mutant::Mutation::Evil } let(:matchable_scopes) { double('Matchable Scopes', length: 10) } before do allow(mutation_a).to receive(:subject).and_return(_subject) allow(mutation_b).to receive(:subject).and_return(_subject) end let(:mutation_a) do double( 'Mutation', identification: 'mutation_id-a', class: mutation_class, original_source: 'true', source: mutation_source ) end let(:mutation_b) do double( 'Mutation', identification: 'mutation_id-b', class: mutation_class, original_source: 'true', source: mutation_source ) end let(:mutation_source) { 'false' } let(:_subject) do double( 'Subject', class: Mutant::Subject, node: s(:true), identification: 'subject_id', mutations: subject_mutations, tests: [ double('Test', identification: 'test_id') ] ) end let(:subject_mutations) { [mutation_a] } let(:test_results) do [ double( 'Test Result', class: Mutant::Result::Test, test: _subject.tests.first, runtime: 1.0, output: 'test-output', success?: mutation_result_success ) ] end let(:mutation_a_result) do double( 'Mutation Result', class: Mutant::Result::Mutation, mutation: mutation_a, killtime: 0.5, runtime: 1.0, index: 0, success?: mutation_result_success, test_results: test_results, failed_test_results: mutation_result_success ? [] : test_results ) end let(:subject_results) do [ Mutant::Result::Subject.new( subject: _subject, runtime: 1.0, mutation_results: [mutation_a_result] ) ] end let(:subjects) { [_subject] } describe '.build' do subject { described_class.build(output) } let(:progressive_format) do described_class::Format::Progressive.new(tty: tty?) end let(:framed_format) do described_class::Format::Framed.new( tty: true, tput: described_class::Tput::INSTANCE ) end before do expect(ENV).to receive(:key?).with('CI').and_return(ci?) end let(:output) { double('Output', tty?: tty?) } let(:tty?) { true } let(:ci?) { false } context 'when not on CI and on a tty' do it { should eql(described_class.new(output, framed_format)) } end context 'when on CI' do let(:ci?) { true } it { should eql(described_class.new(output, progressive_format)) } end context 'when output is not a tty?' do let(:tty?) { false } it { should eql(described_class.new(output, progressive_format)) } end context 'when output does not respond to #tty?' do let(:output) { double('Output') } let(:tty?) { false } it { should eql(described_class.new(output, progressive_format)) } end end describe '#warn' do subject { object.warn(message) } let(:message) { 'message' } it_reports("message\n") end describe '#start' do subject { object.start(env) } context 'on progressive format' do let(:format) { progressive_format } it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] REPORT end context 'on framed format' do it_reports '' end end describe '#progress' do subject { object.progress(collector) } let(:collector) do Mutant::Runner::Collector.new(env) end let(:mutation_result_success) { true } context 'on progressive format' do let(:format) { progressive_format } context 'with empty collector' do it_reports '' end context 'with last mutation present' do before do collector.start(mutation_a) collector.finish(mutation_a_result) end context 'when mutation is successful' do it_reports '.' end context 'when mutation is NOT successful' do let(:mutation_result_success) { false } it_reports 'F' end end end context 'on framed format' do let(:mutation_result_success) { true } context 'with empty collector' do it_reports <<-REPORT Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 0 Runtime: 0.00s Killtime: 0.00s Overhead: NaN% Coverage: 0.00% Expected: 100.00% Active subjects: 0 REPORT end context 'with collector active on one subject' do before do collector.start(mutation_a) end context 'without progress' do it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 0 Runtime: 0.00s Killtime: 0.00s Overhead: NaN% Coverage: 0.00% Expected: 100.00% Active subjects: 1 subject_id mutations: 1 - test_id (00/01) 0% - killtime: 0.00s runtime: 0.00s overhead: 0.00s REPORT end context 'with progress' do let(:subject_mutations) { [mutation_a, mutation_b] } before do collector.start(mutation_b) collector.finish(mutation_a_result) end context 'on failure' do let(:mutation_result_success) { false } it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 2 Kills: 0 Alive: 1 Runtime: 0.00s Killtime: 0.50s Overhead: -100.00% Coverage: 0.00% Expected: 100.00% Active subjects: 1 subject_id mutations: 2 - test_id F (00/02) 0% - killtime: 0.50s runtime: 1.00s overhead: 0.50s REPORT end context 'on success' do it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 2 Kills: 1 Alive: 0 Runtime: 0.00s Killtime: 0.50s Overhead: -100.00% Coverage: 100.00% Expected: 100.00% Active subjects: 1 subject_id mutations: 2 - test_id . (01/02) 100% - killtime: 0.50s runtime: 1.00s overhead: 0.50s REPORT end end end end describe '#report' do subject { object.report(result) } context 'with full coverage' do let(:mutation_result_success) { true } it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 1 Alive: 0 Runtime: 1.10s Killtime: 0.50s Overhead: 120.00% Coverage: 100.00% Expected: 100.00% REPORT end context 'and partial coverage' do let(:mutation_result_success) { false } context 'on evil mutation' do context 'with a diff' do it_reports(<<-REPORT) subject_id - test_id mutation_id-a @@ -1,2 +1,2 @@ -true +false ----------------------- Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 1 Runtime: 1.10s Killtime: 0.50s Overhead: 120.00% Coverage: 0.00% Expected: 100.00% REPORT end context 'without a diff' do let(:mutation_source) { 'true' } it_reports(<<-REPORT) subject_id - test_id mutation_id-a Original source: true Mutated Source: true BUG: Mutation NOT resulted in exactly one diff. Please report a reproduction! ----------------------- Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 1 Runtime: 1.10s Killtime: 0.50s Overhead: 120.00% Coverage: 0.00% Expected: 100.00% REPORT end end context 'on neutral mutation' do let(:mutation_class) { Mutant::Mutation::Neutral } let(:mutation_source) { 'true' } it_reports(<<-REPORT) subject_id - test_id mutation_id-a --- Neutral failure --- Original code was inserted unmutated. And the test did NOT PASS. Your tests do not pass initially or you found a bug in mutant / unparser. Subject AST: (true) Unparsed Source: true Test Reports: 1 - test_id / runtime: 1.0 Test Output: test-output ----------------------- Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 1 Runtime: 1.10s Killtime: 0.50s Overhead: 120.00% Coverage: 0.00% Expected: 100.00% REPORT end context 'on noop mutation' do let(:mutation_class) { Mutant::Mutation::Noop } it_reports(<<-REPORT) subject_id - test_id mutation_id-a ---- Noop failure ----- No code was inserted. And the test did NOT PASS. This is typically a problem of your specs not passing unmutated. Test Reports: 1 - test_id / runtime: 1.0 Test Output: test-output ----------------------- Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: [] Requires: [] Available Subjects: 1 Subjects: 1 Mutations: 1 Kills: 0 Alive: 1 Runtime: 1.10s Killtime: 0.50s Overhead: 120.00% Coverage: 0.00% Expected: 100.00% REPORT end end context 'without subjects' do let(:subjects) { [] } let(:subject_results) { [] } let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, includes: %w[include-dir], requires: %w[require-name]) } it_reports(<<-REPORT) Mutant configuration: Matcher: # Integration: null Expect Coverage: 100.00% Jobs: 1 Includes: ["include-dir"] Requires: ["require-name"] Available Subjects: 0 Subjects: 0 Mutations: 0 Kills: 0 Alive: 0 Runtime: 1.10s Killtime: 0.00s Overhead: Inf% Coverage: 0.00% Expected: 100.00% REPORT end end end end