crispr 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -0
- data/bin/crispr +6 -0
- data/lib/crispr/cli.rb +55 -0
- data/lib/crispr/mutations/boolean_mutations.rb +28 -0
- data/lib/crispr/mutator.rb +33 -0
- data/lib/crispr/reporter.rb +41 -0
- data/lib/crispr/runner.rb +35 -0
- data/lib/crispr/version.rb +1 -1
- data/lib/crispr.rb +3 -0
- metadata +40 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d070d677dfe3f0926fff8ddf0a2f31aae859ed65a5fdeb76164eaf180a2fc921
|
4
|
+
data.tar.gz: 1a76d002b52a92f4ff756eb11cac032dbaccda83e23fec8bbbfa4ababbfe64a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bdf7c8476653270d00188101b87ae5b3de8fbcd6c6d5c572211ed35833dbbd88ad3f9e69fe9abd3921567e7914a50899bc071071f50547dc273b560b0823a5e
|
7
|
+
data.tar.gz: cf43c052481ebd7f10d4eaa67056d46e8e85e76419af7675e5b960b743a096e22cbd967bffebfbf86099e693e9f092847f9344e44748c1e5101dac298255ce97
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,29 @@
|
|
1
|
+
plugins:
|
2
|
+
- rubocop-rake
|
3
|
+
- rubocop-rspec
|
4
|
+
|
1
5
|
AllCops:
|
6
|
+
NewCops: enable
|
2
7
|
TargetRubyVersion: 3.1
|
3
8
|
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Metrics/CyclomaticComplexity:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 100
|
17
|
+
|
18
|
+
Metrics/PerceivedComplexity:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
RSpec/ExampleLength:
|
22
|
+
Max: 20
|
23
|
+
|
24
|
+
RSpec/MultipleExpectations:
|
25
|
+
Enabled: false
|
26
|
+
|
4
27
|
Style/StringLiterals:
|
5
28
|
EnforcedStyle: double_quotes
|
6
29
|
|
data/bin/crispr
ADDED
data/lib/crispr/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "crispr/mutator"
|
4
|
+
require "crispr/runner"
|
5
|
+
require "crispr/reporter"
|
6
|
+
|
7
|
+
module Crispr
|
8
|
+
# Provides the command-line interface for the Crispr gem.
|
9
|
+
# Handles argument parsing and invokes the mutation and runner logic.
|
10
|
+
class CLI
|
11
|
+
def self.run(argv = ARGV)
|
12
|
+
command = argv.shift
|
13
|
+
case command
|
14
|
+
when "run"
|
15
|
+
input_path = argv.shift
|
16
|
+
unless input_path && File.exist?(input_path)
|
17
|
+
puts "Error: Please specify a valid Ruby file or directory to mutate."
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
paths = File.directory?(input_path) ? Dir.glob("#{input_path}/**/*.rb") : [input_path]
|
22
|
+
|
23
|
+
reporter = Crispr::Reporter.new
|
24
|
+
|
25
|
+
paths.each do |path|
|
26
|
+
source = File.read(path)
|
27
|
+
mutator = Crispr::Mutator.new(source)
|
28
|
+
mutations = mutator.mutations
|
29
|
+
|
30
|
+
if mutations.empty?
|
31
|
+
puts "No mutations found in #{path}."
|
32
|
+
next
|
33
|
+
end
|
34
|
+
|
35
|
+
mutations.each_with_index do |mutated, index|
|
36
|
+
puts "Running mutation #{index + 1}/#{mutations.size} on #{path}..."
|
37
|
+
killed = Crispr::Runner.run_mutation(path: path, mutated_source: mutated)
|
38
|
+
reporter.record(killed: killed)
|
39
|
+
puts killed ? "💥 Mutation killed" : "⚠️ Mutation survived"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
summary = reporter.summary
|
44
|
+
puts
|
45
|
+
puts "Mutations: #{summary[:mutations]}"
|
46
|
+
puts "💥 Killed: #{summary[:killed]}"
|
47
|
+
puts "⚠️ Survived: #{summary[:survived]}"
|
48
|
+
puts "Score: #{summary[:score]}%"
|
49
|
+
else
|
50
|
+
puts "Usage: crispr run path/to/file.rb"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# rubocop:disable Lint/BooleanSymbol
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Crispr
|
5
|
+
module Mutations
|
6
|
+
# Provides boolean-specific AST mutations.
|
7
|
+
# Currently supports toggling `true` to `false` and `false` to `true`.
|
8
|
+
module BooleanMutations
|
9
|
+
# Returns a list of stringified mutated forms for the given boolean AST node.
|
10
|
+
#
|
11
|
+
# @param node [Parser::AST::Node] the AST node to inspect
|
12
|
+
# @return [Array<String>] mutated Ruby source code strings
|
13
|
+
def self.mutations_for(node)
|
14
|
+
return [] unless node.is_a?(Parser::AST::Node)
|
15
|
+
|
16
|
+
case node.type
|
17
|
+
when :true
|
18
|
+
[Unparser.unparse(Parser::AST::Node.new(:false))]
|
19
|
+
when :false
|
20
|
+
[Unparser.unparse(Parser::AST::Node.new(:true))]
|
21
|
+
else
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# rubocop:enable Lint/BooleanSymbol
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "parser/current"
|
4
|
+
require "unparser"
|
5
|
+
require_relative "mutations/boolean_mutations"
|
6
|
+
|
7
|
+
module Crispr
|
8
|
+
# Mutator performs simple AST mutations on Ruby source code.
|
9
|
+
# Currently, it supports changing `true` literals to `false` and vice versa.
|
10
|
+
class Mutator
|
11
|
+
def initialize(source_code)
|
12
|
+
@source_code = source_code
|
13
|
+
end
|
14
|
+
|
15
|
+
def mutations
|
16
|
+
ast = Parser::CurrentRuby.parse(@source_code)
|
17
|
+
return [] unless ast
|
18
|
+
|
19
|
+
find_mutations(ast)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def find_mutations(node)
|
25
|
+
return [] unless node.is_a?(Parser::AST::Node)
|
26
|
+
|
27
|
+
local_mutations = Crispr::Mutations::BooleanMutations.mutations_for(node)
|
28
|
+
child_mutations = node.children.flat_map { |child| find_mutations(child) }
|
29
|
+
|
30
|
+
local_mutations + child_mutations
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Crispr
|
4
|
+
# Reporter collects mutation testing results and prints a summary.
|
5
|
+
class Reporter
|
6
|
+
# Initializes a new Reporter.
|
7
|
+
def initialize
|
8
|
+
@killed = 0
|
9
|
+
@survived = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
# Records the result of a mutation test.
|
13
|
+
#
|
14
|
+
# @param killed [Boolean] whether the mutation was killed
|
15
|
+
# @return [void]
|
16
|
+
def record(killed:)
|
17
|
+
killed ? @killed += 1 : @survived += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the mutation score as a percentage.
|
21
|
+
#
|
22
|
+
# @return [Float] the mutation score
|
23
|
+
def score
|
24
|
+
total = @killed + @survived
|
25
|
+
total.zero? ? 0.0 : (@killed.to_f / total * 100).round(2)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a summary of mutation results.
|
29
|
+
#
|
30
|
+
# @return [Hash] summary statistics including totals and score
|
31
|
+
def summary
|
32
|
+
total = @killed + @survived
|
33
|
+
{
|
34
|
+
mutations: total,
|
35
|
+
killed: @killed,
|
36
|
+
survived: @survived,
|
37
|
+
score: score
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module Crispr
|
6
|
+
# Runner is responsible for executing tests against mutated code.
|
7
|
+
# It temporarily replaces the file's source with a mutated version,
|
8
|
+
# runs the test suite, and restores the original file afterward.
|
9
|
+
class Runner
|
10
|
+
# Runs the test suite with the mutated source code for the given file path.
|
11
|
+
#
|
12
|
+
# @param path [String] the path to the source file to mutate
|
13
|
+
# @param mutated_source [String] the mutated version of the file's source code
|
14
|
+
# @param test_path [String, nil] optional path to a specific test file to run
|
15
|
+
# @return [Boolean] true if the mutation was killed (test suite failed), false otherwise
|
16
|
+
def self.run_mutation(path:, mutated_source:, test_path: nil)
|
17
|
+
original_source = File.read(path)
|
18
|
+
|
19
|
+
begin
|
20
|
+
File.write(path, mutated_source)
|
21
|
+
|
22
|
+
test_cmd = test_path ? "bundle exec rspec #{test_path}" : "bundle exec rspec"
|
23
|
+
stdout, stderr, status = Open3.capture3(test_cmd)
|
24
|
+
killed = !status.success?
|
25
|
+
|
26
|
+
puts stdout unless status.success?
|
27
|
+
puts stderr unless status.success?
|
28
|
+
|
29
|
+
killed
|
30
|
+
ensure
|
31
|
+
File.write(path, original_source)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/crispr/version.rb
CHANGED
data/lib/crispr.rb
CHANGED
metadata
CHANGED
@@ -1,19 +1,48 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crispr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron F Stanton
|
8
|
-
bindir:
|
8
|
+
bindir: bin
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
-
dependencies:
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: parser
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: unparser
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
12
40
|
description: Crispr is a mutation testing tool for Ruby that introduces small code
|
13
41
|
mutations to verify the effectiveness of your test suite.
|
14
42
|
email:
|
15
43
|
- afstanton@gmail.com
|
16
|
-
executables:
|
44
|
+
executables:
|
45
|
+
- crispr
|
17
46
|
extensions: []
|
18
47
|
extra_rdoc_files: []
|
19
48
|
files:
|
@@ -22,7 +51,13 @@ files:
|
|
22
51
|
- LICENSE.txt
|
23
52
|
- README.md
|
24
53
|
- Rakefile
|
54
|
+
- bin/crispr
|
25
55
|
- lib/crispr.rb
|
56
|
+
- lib/crispr/cli.rb
|
57
|
+
- lib/crispr/mutations/boolean_mutations.rb
|
58
|
+
- lib/crispr/mutator.rb
|
59
|
+
- lib/crispr/reporter.rb
|
60
|
+
- lib/crispr/runner.rb
|
26
61
|
- lib/crispr/version.rb
|
27
62
|
- sig/crispr.rbs
|
28
63
|
homepage: https://github.com/afstanton/crispr
|
@@ -33,6 +68,7 @@ metadata:
|
|
33
68
|
homepage_uri: https://github.com/afstanton/crispr
|
34
69
|
source_code_uri: https://github.com/afstanton/crispr
|
35
70
|
changelog_uri: https://github.com/afstanton/crispr/blob/main/CHANGELOG.md
|
71
|
+
rubygems_mfa_required: 'true'
|
36
72
|
rdoc_options: []
|
37
73
|
require_paths:
|
38
74
|
- lib
|