Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout as configurable coverage criteria #1118

Merged
merged 4 commits into from
Nov 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# v0.10.7 unreleased
# v0.10.7 2020-11-22

* Add support for external mutation timeouts.

New config file settings. `mutation_timeout` and `coverage_criteria`
to control timeouts and coverage conditions.

- [#1105](https://github.com/mbj/mutant/pull/1105)
- [#1118](https://github.com/mbj/mutant/pull/1118)

* Improve CLI reporting to be less noisy:
- [#1117](https://github.com/mbj/mutant/pull/1117)
- [#1106](https://github.com/mbj/mutant/pull/1106)

* Add support for external mutation timeouts. [#1105](https://github.com/mbj/mutant/pull/1105)
* Fix crash on static send mutation. [#1108](https://github.com/mbj/mutant/pull/1108)
* Change CLI report format to print diff last.
This should be more user friendly on incremental / interactive
operation.

* Add more verbose configuration [documentation](https://github.com/mbj/mutant/blob/master/docs/configuration.md).

# v0.10.6 2020-11-06

Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
mutant (0.10.6)
mutant (0.10.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
anima (~> 0.3.1)
Expand Down
41 changes: 37 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The includes key takes a list of strings to add to ruby's `$LOAD_PATH`. This is
```yml
---
includes:
- lib
- lib
```

Additional includes can be added by providing the `-I` or `--include` option to the CLI.
Expand All @@ -23,7 +23,7 @@ The requires key takes a list of ruby files to be required. This is how mutant l
```yml
---
requires:
- my_app
- my_app
```

Additional requires can be added by providing the `-r` or `--require` option to the CLI.
Expand Down Expand Up @@ -63,5 +63,38 @@ See mutant's configuration file, [mutant.yml](/mutant.yml), for a complete examp
#### `mutation_timeout`

Specify the maximum time, in seconds, a mutation gets analysed.
Past this time the mutation analysis gets terminated and the result is currently being
reported as uncovered.

```yml
---
# Control the maximum time per mutation spend on analysis.
# Unit is in fractional seconds.
#
# Default absent.
#
# Absent value: No limit on per mutation analysis time.
# Present value: Limit per mutation analysis time to specified value in seconds.
mutation_timeout: 1.0
```

Use `timeout` setting under `coverage_criteria` in the config file to control
if timeouts are allowed to cover mutations.

#### `coverage_criteria`

A configuration file only setting to control which criteria apply to determine mutation coverage.

```yml
---
coverage_criteria:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to set this via CLI flag? Once we have per-subject / per file config then customizing it that way would be good but I am thinking of doing something like having the fork abort coverage off in my local config (so I can find and report bugs) but considering it as a kill in CI so that PRs are never gated by segfaults.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing to configure each subject separately is the next step.

But: Its unlikely it makes sense in the CLI, simply because the CLI would become very long. My next initiative would be to support inheriting (prefix match) based per subject configuration in the code, and use the config file as input.

Than the overnext step to support it from in-line code comments.

Only after that I'll consider making CLI (per subject) support for these.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, yeah, I think that makes sense. Looking forward to it 👍

# Control the timeout criteria, defaults to `false`:
# * `true` - mutant will consider timeouts as covering mutations.
# * `false` mutant will ignore timeouts when determining mutation coverage.
timeout: false
# Control the test result criteria, # defaults to `true`
# * `true` mutant will consider failing tests covering mutations.
# * `false` mutant will ignore test results when determining mutation coverage.
# Hint: You probably do not want to touch the default.
test_result: true
```

At this point there is no CLI equivalent for these settings.
2 changes: 2 additions & 0 deletions lib/mutant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ module Mutant
require 'mutant/reporter/cli'
require 'mutant/reporter/cli/printer'
require 'mutant/reporter/cli/printer/config'
require 'mutant/reporter/cli/printer/coverage_result'
require 'mutant/reporter/cli/printer/env'
require 'mutant/reporter/cli/printer/env_progress'
require 'mutant/reporter/cli/printer/env_result'
Expand Down Expand Up @@ -223,6 +224,7 @@ module Mutant
# Reopen class to initialize constant to avoid dep circle
class Config
DEFAULT = new(
coverage_criteria: Config::CoverageCriteria::DEFAULT,
expression_parser: Expression::Parser.new([
Expression::Method,
Expression::Methods,
Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/cli/command/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def zombie?

def initialize(attributes)
super(attributes)
@config = Config::DEFAULT
@config = Config.env
end

def execute
Expand All @@ -49,7 +49,7 @@ def execute
end

def expand(file_config)
@config = Config.env.merge(file_config).merge(@config)
@config = @config.merge(file_config)
end

def soft_fail(result)
Expand Down
81 changes: 51 additions & 30 deletions lib/mutant/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Mutant
# to current environment is being represented by the Mutant::Env object.
class Config
include Adamantium::Flat, Anima.new(
:coverage_criteria,
:expression_parser,
:fail_fast,
:includes,
Expand All @@ -24,32 +25,6 @@ class Config
define_method(:"#{name}?") { public_send(name) }
end

boolean = Transform::Boolean.new
float = Transform::Primitive.new(Float)
integer = Transform::Primitive.new(Integer)
string = Transform::Primitive.new(String)

string_array = Transform::Array.new(string)

TRANSFORM = Transform::Sequence.new(
[
Transform::Exception.new(SystemCallError, :read.to_proc),
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
Transform::Hash.new(
optional: [
Transform::Hash::Key.new('fail_fast', boolean),
Transform::Hash::Key.new('includes', string_array),
Transform::Hash::Key.new('integration', string),
Transform::Hash::Key.new('jobs', integer),
Transform::Hash::Key.new('mutation_timeout', float),
Transform::Hash::Key.new('requires', string_array)
],
required: []
),
Transform::Hash::Symbolize.new
]
)

MORE_THAN_ONE_CONFIG_FILE = <<~'MESSAGE'
Found more than one candidate for use as implicit config file: %s
MESSAGE
Expand All @@ -60,6 +35,32 @@ class Config
mutant.yml
].freeze

private_constant(*constants(false))

class CoverageCriteria
include Anima.new(:timeout, :test_result)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgollahon The next step is to allow to configure the coverage criteria per subject in the config file an in-line.

Same for the mutation timeout.

The ideal setting than is a global "high" timeout with coverage_criteria.timeout = false and a per subject one after manual review (From an alive, example an infinite loop) setting it to true.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also there are more possible criteria like warnings etc.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the most important criteria for @pawelpacana counting ruby aborts as killed.

#1054.

Next major feature for 0.10.8. The infrastructure exists now.

Per subject config will be added in 0.10.9 possibly also in-line configuration.


DEFAULT = new(
timeout: false,
test_result: true
)

TRANSFORM =
Transform::Sequence.new(
[
Transform::Hash.new(
optional: [
Transform::Hash::Key.new('timeout', Transform::BOOLEAN),
Transform::Hash::Key.new('test_result', Transform::BOOLEAN)
],
required: []
),
Transform::Hash::Symbolize.new,
->(value) { Either::Right.new(DEFAULT.with(**value)) }
]
)
end # CoverageCriteria

# Merge with other config
#
# @param [Config] other
Expand All @@ -68,18 +69,16 @@ class Config
def merge(other)
other.with(
fail_fast: fail_fast || other.fail_fast,
includes: includes + other.includes,
includes: other.includes + includes,
jobs: other.jobs || jobs,
integration: other.integration || integration,
mutation_timeout: other.mutation_timeout || mutation_timeout,
matcher: matcher.merge(other.matcher),
requires: requires + other.requires,
requires: other.requires + requires,
zombie: zombie || other.zombie
)
end

private_constant(*constants(false))

# Load config file
#
# @param [World] world
Expand Down Expand Up @@ -113,5 +112,27 @@ def self.load_contents(path)
def self.env
DEFAULT.with(jobs: Etc.nprocessors)
end

TRANSFORM = Transform::Sequence.new(
[
Transform::Exception.new(SystemCallError, :read.to_proc),
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
Transform::Hash.new(
optional: [
Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
Transform::Hash::Key.new('integration', Transform::STRING),
Transform::Hash::Key.new('jobs', Transform::INTEGER),
Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
],
required: []
),
Transform::Hash::Symbolize.new
]
)

private_constant(:TRANSFORM)
end # Config
end # Mutant
2 changes: 1 addition & 1 deletion lib/mutant/isolation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Result
# Test for successful result
#
# @return [Boolean]
def success?
def valid_value?
timeout.nil? && exception.nil? && (process_status.nil? || process_status.success?)
end
end # Result
Expand Down
19 changes: 19 additions & 0 deletions lib/mutant/reporter/cli/printer/coverage_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Mutant
class Reporter
class CLI
class Printer
# Reporter for mutation coverage results
class CoverageResult < self
# Run report printer
#
# @return [undefined]
def run
visit(MutationResult, object.mutation_result)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pass through ATM, I could evision a verbose mode where we show covered mutations, and tell why they where killed.

end
end # Printer
end # Coverage
end # CLI
end # Reporter
end # Mutant
2 changes: 2 additions & 0 deletions lib/mutant/reporter/cli/printer/env_progress.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class EnvProgress < self
:amount_mutation_results,
:amount_mutations_alive,
:amount_mutations_killed,
:amount_timeouts,
:coverage,
:env,
:killtime,
Expand All @@ -21,6 +22,7 @@ class EnvProgress < self
[:info, 'Results: %s', :amount_mutation_results],
[:info, 'Kills: %s', :amount_mutations_killed],
[:info, 'Alive: %s', :amount_mutations_alive ],
[:info, 'Timeouts: %s', :amount_timeouts ],
[:info, 'Runtime: %0.2fs', :runtime ],
[:info, 'Killtime: %0.2fs', :killtime ],
[:info, 'Overhead: %0.2f%%', :overhead_percent ],
Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/reporter/cli/printer/subject_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Printer
# Subject result printer
class SubjectResult < self

delegate :subject, :alive_mutation_results, :tests
delegate :subject, :uncovered_results, :tests
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slowly moving the wording away from "alive" and "killed" to "uncovered" and "covered".


# Run report printer
#
Expand All @@ -17,7 +17,7 @@ def run
tests.each do |test|
puts("- #{test.identification}")
end
visit_collection(MutationResult, alive_mutation_results)
visit_collection(CoverageResult, uncovered_results)
end

end # SubjectResult
Expand Down
Loading