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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 321944f554596b5fd91d57f1e86190394c64d6d46d5108a13d587a60798902ab
4
- data.tar.gz: f20d189e725811d51b849714654c89e3d36fc553bfb80723208a0baf2d3a545b
3
+ metadata.gz: d070d677dfe3f0926fff8ddf0a2f31aae859ed65a5fdeb76164eaf180a2fc921
4
+ data.tar.gz: 1a76d002b52a92f4ff756eb11cac032dbaccda83e23fec8bbbfa4ababbfe64a9
5
5
  SHA512:
6
- metadata.gz: 74b9b78ef2c606dbaecaa0459fc21f98b25c5ec9b47ea930d6f8dd3b3638e81e3f7534f899fcf971138f349e592bef98c72bdbd497a5919de42e66887e9e9f08
7
- data.tar.gz: d3c74eee1b0533eb806141b69e2d8c58b66fac2ab5aea1fc3a98a7835d5c1c603cf42d5e2825d458ee622335cd2fe949a25ad137eae6268472d4233dfee434d6
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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "crispr"
5
+
6
+ Crispr::CLI.run
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crispr
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/crispr.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "crispr/version"
4
+ require_relative "crispr/mutator"
5
+ require_relative "crispr/runner"
6
+ require_relative "crispr/cli"
4
7
 
5
8
  module Crispr
6
9
  class Error < StandardError; end
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.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron F Stanton
8
- bindir: exe
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