mutant 0.10.17 → 0.10.22

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +3 -0
  3. data/lib/mutant/ast/meta/send.rb +0 -1
  4. data/lib/mutant/ast/types.rb +0 -9
  5. data/lib/mutant/bootstrap.rb +2 -2
  6. data/lib/mutant/cli/command/environment.rb +4 -4
  7. data/lib/mutant/cli/command/environment/run.rb +2 -2
  8. data/lib/mutant/cli/command/environment/show.rb +1 -2
  9. data/lib/mutant/cli/command/environment/subject.rb +39 -0
  10. data/lib/mutant/cli/command/root.rb +1 -1
  11. data/lib/mutant/cli/command/subscription.rb +1 -1
  12. data/lib/mutant/env.rb +8 -6
  13. data/lib/mutant/expression.rb +12 -3
  14. data/lib/mutant/expression/method.rb +33 -8
  15. data/lib/mutant/expression/methods.rb +6 -4
  16. data/lib/mutant/expression/namespace.rb +17 -6
  17. data/lib/mutant/expression/parser.rb +2 -2
  18. data/lib/mutant/integration/null.rb +2 -3
  19. data/lib/mutant/isolation/fork.rb +1 -1
  20. data/lib/mutant/license.rb +1 -1
  21. data/lib/mutant/license/subscription.rb +1 -1
  22. data/lib/mutant/license/subscription/commercial.rb +1 -1
  23. data/lib/mutant/license/subscription/opensource.rb +1 -1
  24. data/lib/mutant/mutator/node/arguments.rb +0 -2
  25. data/lib/mutant/mutator/node/block.rb +5 -1
  26. data/lib/mutant/mutator/node/defined.rb +12 -3
  27. data/lib/mutant/mutator/node/kwargs.rb +34 -0
  28. data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
  29. data/lib/mutant/mutator/node/procarg_zero.rb +0 -6
  30. data/lib/mutant/mutator/node/send.rb +20 -18
  31. data/lib/mutant/parallel.rb +43 -28
  32. data/lib/mutant/parallel/driver.rb +9 -3
  33. data/lib/mutant/parallel/worker.rb +60 -2
  34. data/lib/mutant/pipe.rb +94 -0
  35. data/lib/mutant/reporter/cli/printer/isolation_result.rb +1 -6
  36. data/lib/mutant/result.rb +9 -6
  37. data/lib/mutant/runner.rb +8 -11
  38. data/lib/mutant/runner/sink.rb +12 -2
  39. data/lib/mutant/subject/method/instance.rb +1 -1
  40. data/lib/mutant/test.rb +1 -1
  41. data/lib/mutant/timer.rb +2 -4
  42. data/lib/mutant/transform.rb +0 -2
  43. data/lib/mutant/version.rb +1 -1
  44. metadata +12 -9
@@ -12,7 +12,7 @@ module Mutant
12
12
  # @return [Either<String,Subscription>]
13
13
  #
14
14
  # @api private
15
- def self.apply(world)
15
+ def self.call(world)
16
16
  load_mutant_license(world)
17
17
  .fmap { license_path(world) }
18
18
  .bind { |path| Subscription.load(world, world.json.load(path)) }
@@ -30,7 +30,7 @@ module Mutant
30
30
  'oss' => Opensource
31
31
  }.fetch(value.fetch('type'))
32
32
  .from_json(value.fetch('contents'))
33
- .apply(world)
33
+ .call(world)
34
34
  end
35
35
 
36
36
  # Subscription self description
@@ -15,7 +15,7 @@ module Mutant
15
15
  new(value.fetch('authors').map(&Author.public_method(:new)).to_set)
16
16
  end
17
17
 
18
- def apply(world)
18
+ def call(world)
19
19
  candidates = candidates(world)
20
20
 
21
21
  if (licensed & candidates).any?
@@ -50,7 +50,7 @@ module Mutant
50
50
  )
51
51
  end
52
52
 
53
- def apply(world)
53
+ def call(world)
54
54
  world
55
55
  .capture_stdout(%w[git remote --verbose])
56
56
  .fmap(&method(:parse_remotes))
@@ -8,8 +8,6 @@ module Mutant
8
8
 
9
9
  handle(:args)
10
10
 
11
- PROCARG = %i[restarg mlhs].freeze
12
-
13
11
  private
14
12
 
15
13
  def dispatch
@@ -21,7 +21,7 @@ module Mutant
21
21
  end
22
22
 
23
23
  def mutate_body
24
- emit_body(nil)
24
+ emit_body(nil) unless unconditional_loop?
25
25
  emit_body(N_RAISE)
26
26
 
27
27
  return unless body
@@ -31,6 +31,10 @@ module Mutant
31
31
  mutate_body_receiver
32
32
  end
33
33
 
34
+ def unconditional_loop?
35
+ send.eql?(s(:send, nil, :loop))
36
+ end
37
+
34
38
  def body_has_control?
35
39
  AST.find_last_path(body) do |node|
36
40
  n_break?(node) || n_next?(node)
@@ -13,10 +13,19 @@ module Mutant
13
13
  private
14
14
 
15
15
  def dispatch
16
- emit_singletons
17
- emit(N_TRUE)
16
+ emit(N_NIL)
17
+ emit_instance_variable_mutation
18
+ end
19
+
20
+ def emit_instance_variable_mutation
21
+ return unless n_ivar?(expression)
22
+
23
+ instance_variable_name = Mutant::Util.one(expression.children)
18
24
 
19
- emit_expression_mutations { |node| !n_self?(node) }
25
+ emit(
26
+ s(:send, nil, :instance_variable_defined?,
27
+ s(:sym, instance_variable_name))
28
+ )
20
29
  end
21
30
 
22
31
  end # Defined
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ # Mutator for kwargs node
7
+ class Kwargs < self
8
+
9
+ handle(:kwargs)
10
+
11
+ private
12
+
13
+ def dispatch
14
+ emit_argument_presence
15
+ emit_argument_mutations
16
+ end
17
+
18
+ def emit_argument_presence
19
+ Util::Array::Presence.call(children).each do |children|
20
+ emit_type(*children)
21
+ end
22
+ end
23
+
24
+ def emit_argument_mutations
25
+ children.each_with_index do |child, index|
26
+ Mutator.mutate(child).each do |mutant|
27
+ emit_child_update(index, mutant)
28
+ end
29
+ end
30
+ end
31
+ end # Kwargs
32
+ end # Node
33
+ end # Mutator
34
+ end # Mutant
@@ -11,8 +11,6 @@ module Mutant
11
11
 
12
12
  children :value
13
13
 
14
- PREFIX = '__mutant__'
15
-
16
14
  private
17
15
 
18
16
  def dispatch
@@ -4,12 +4,6 @@ module Mutant
4
4
  class Mutator
5
5
  class Node
6
6
  class ProcargZero < self
7
- MAP = {
8
- ::Parser::AST::Node => :emit_argument_node_mutations,
9
- Symbol => :emit_argument_symbol_mutations
10
- }.freeze
11
-
12
- private_constant(*constants(false))
13
7
 
14
8
  handle :procarg0
15
9
 
@@ -14,30 +14,32 @@ module Mutant
14
14
  children :receiver, :selector
15
15
 
16
16
  SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
17
- reverse_map: %i[map each],
18
- kind_of?: %i[instance_of?],
17
+ :< => %i[== eql? equal?],
18
+ :<= => %i[< == eql? equal?],
19
+ :== => %i[eql? equal?],
20
+ :> => %i[== eql? equal?],
21
+ :>= => %i[> == eql? equal?],
22
+ __send__: %i[public_send],
23
+ all?: %i[any?],
24
+ any?: %i[all?],
25
+ at: %i[fetch key?],
26
+ eql?: %i[equal?],
27
+ fetch: %i[key?],
28
+ flat_map: %i[map],
29
+ gsub: %i[sub],
19
30
  is_a?: %i[instance_of?],
31
+ kind_of?: %i[instance_of?],
32
+ map: %i[each],
33
+ method: %i[public_method],
20
34
  reverse_each: %i[each],
35
+ reverse_map: %i[map each],
21
36
  reverse_merge: %i[merge],
22
- map: %i[each],
23
- flat_map: %i[map],
24
37
  send: %i[public_send __send__],
25
- __send__: %i[public_send],
26
- method: %i[public_method],
27
- gsub: %i[sub],
28
- eql?: %i[equal?],
29
- to_s: %i[to_str],
30
- to_i: %i[to_int],
31
38
  to_a: %i[to_ary],
32
39
  to_h: %i[to_hash],
33
- at: %i[fetch key?],
34
- fetch: %i[key?],
35
- values_at: %i[fetch_values],
36
- :== => %i[eql? equal?],
37
- :>= => %i[> == eql? equal?],
38
- :<= => %i[< == eql? equal?],
39
- :> => %i[== eql? equal?],
40
- :< => %i[== eql? equal?]
40
+ to_i: %i[to_int],
41
+ to_s: %i[to_str],
42
+ values_at: %i[fetch_values]
41
43
  )
42
44
 
43
45
  RECEIVER_SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
@@ -6,45 +6,61 @@ module Mutant
6
6
 
7
7
  # Run async computation returning driver
8
8
  #
9
+ # @param [World] world
9
10
  # @param [Config] config
10
11
  #
11
12
  # @return [Driver]
12
- def self.async(config)
13
- shared = {
14
- var_active_jobs: shared(Variable::IVar, config, value: Set.new),
15
- var_final: shared(Variable::IVar, config),
16
- var_sink: shared(Variable::IVar, config, value: config.sink)
17
- }
13
+ def self.async(world, config)
14
+ shared = shared_state(world, config)
15
+ workers = workers(world, config, shared)
18
16
 
19
17
  Driver.new(
20
- threads: threads(config, worker(config, **shared)),
18
+ workers: workers,
19
+ threads: threads(world, config, workers),
21
20
  **shared
22
21
  )
23
22
  end
24
23
 
25
- # The worker
26
- #
27
- # @param [Config] config
28
- #
29
- # @return [Worker]
30
- def self.worker(config, **shared)
31
- Worker.new(
32
- processor: config.processor,
33
- var_running: shared(Variable::MVar, config, value: config.jobs),
34
- var_source: shared(Variable::IVar, config, value: config.source),
35
- **shared
36
- )
24
+ def self.workers(world, config, shared)
25
+ Array.new(config.jobs) do |index|
26
+ Worker.start(
27
+ block: config.block,
28
+ index: index,
29
+ process_name: "#{config.process_name}-#{index}",
30
+ world: world,
31
+ **shared
32
+ )
33
+ end
37
34
  end
35
+ private_class_method :workers
36
+
37
+ def self.shared_state(world, config)
38
+ {
39
+ var_active_jobs: shared(Variable::IVar, world, value: Set.new),
40
+ var_final: shared(Variable::IVar, world),
41
+ var_running: shared(Variable::MVar, world, value: config.jobs),
42
+ var_sink: shared(Variable::IVar, world, value: config.sink),
43
+ var_source: shared(Variable::IVar, world, value: config.source)
44
+ }
45
+ end
46
+ private_class_method :shared_state
47
+
48
+ def self.threads(world, config, workers)
49
+ thread = world.thread
38
50
 
39
- def self.threads(config, worker)
40
- Array.new(config.jobs) { config.thread.new(&worker.method(:call)) }
51
+ workers.map do |worker|
52
+ thread.new do
53
+ thread.current.name = "#{config.thread_name}-#{worker.index}"
54
+ worker.call
55
+ end
56
+ end
41
57
  end
42
58
  private_class_method :threads
43
59
 
44
- def self.shared(klass, config, **attributes)
60
+ def self.shared(klass, world, **attributes)
45
61
  klass.new(
46
- condition_variable: config.condition_variable,
47
- mutex: config.mutex,
62
+ condition_variable: world.condition_variable,
63
+ mutex: world.mutex,
48
64
  **attributes
49
65
  )
50
66
  end
@@ -75,13 +91,12 @@ module Mutant
75
91
  # Parallel run configuration
76
92
  class Config
77
93
  include Adamantium::Flat, Anima.new(
78
- :condition_variable,
94
+ :block,
79
95
  :jobs,
80
- :mutex,
81
- :processor,
96
+ :process_name,
82
97
  :sink,
83
98
  :source,
84
- :thread
99
+ :thread_name
85
100
  )
86
101
  end # Config
87
102
 
@@ -8,7 +8,10 @@ module Mutant
8
8
  :threads,
9
9
  :var_active_jobs,
10
10
  :var_final,
11
- :var_sink
11
+ :var_running,
12
+ :var_sink,
13
+ :var_source,
14
+ :workers
12
15
  )
13
16
 
14
17
  private(*anima.attribute_names)
@@ -29,7 +32,10 @@ module Mutant
29
32
 
30
33
  def finalize(status)
31
34
  status.tap do
32
- threads.each(&:join) if status.done?
35
+ if status.done?
36
+ workers.each(&:join)
37
+ threads.each(&:join)
38
+ end
33
39
  end
34
40
  end
35
41
 
@@ -38,7 +44,7 @@ module Mutant
38
44
  var_sink.with do |sink|
39
45
  Status.new(
40
46
  active_jobs: active_jobs.dup.freeze,
41
- done: threads.all? { |thread| !thread.alive? },
47
+ done: threads.all? { |worker| !worker.alive? },
42
48
  payload: sink.status
43
49
  )
44
50
  end
@@ -4,7 +4,10 @@ module Mutant
4
4
  module Parallel
5
5
  class Worker
6
6
  include Adamantium::Flat, Anima.new(
7
- :processor,
7
+ :connection,
8
+ :index,
9
+ :pid,
10
+ :process,
8
11
  :var_active_jobs,
9
12
  :var_final,
10
13
  :var_running,
@@ -14,6 +17,45 @@ module Mutant
14
17
 
15
18
  private(*anima.attribute_names)
16
19
 
20
+ public :index
21
+
22
+ # rubocop:disable Metrics/MethodLength
23
+ # rubocop:disable Metrics/ParameterLists
24
+ def self.start(world:, block:, process_name:, **attributes)
25
+ io = world.io
26
+ process = world.process
27
+
28
+ request = Pipe.from_io(io)
29
+ response = Pipe.from_io(io)
30
+
31
+ pid = process.fork do
32
+ world.thread.current.name = process_name
33
+ world.process.setproctitle(process_name)
34
+
35
+ Child.new(
36
+ block: block,
37
+ connection: Pipe::Connection.from_pipes(
38
+ marshal: world.marshal,
39
+ reader: request,
40
+ writer: response
41
+ )
42
+ ).call
43
+ end
44
+
45
+ new(
46
+ pid: pid,
47
+ process: process,
48
+ connection: Pipe::Connection.from_pipes(
49
+ marshal: world.marshal,
50
+ reader: response,
51
+ writer: request
52
+ ),
53
+ **attributes
54
+ )
55
+ end
56
+ # rubocop:enable Metrics/MethodLength
57
+ # rubocop:enable Metrics/ParameterLists
58
+
17
59
  # Run worker payload
18
60
  #
19
61
  # @return [self]
@@ -23,7 +65,7 @@ module Mutant
23
65
 
24
66
  job_start(job)
25
67
 
26
- result = processor.call(job.payload)
68
+ result = connection.call(job.payload)
27
69
 
28
70
  job_done(job)
29
71
 
@@ -35,6 +77,12 @@ module Mutant
35
77
  self
36
78
  end
37
79
 
80
+ def join
81
+ process.kill('TERM', pid)
82
+ process.wait(pid)
83
+ self
84
+ end
85
+
38
86
  private
39
87
 
40
88
  def next_job
@@ -66,6 +114,16 @@ module Mutant
66
114
  var_final.put(nil) if var_running.modify(&:pred).zero?
67
115
  end
68
116
 
117
+ class Child
118
+ include Anima.new(:block, :connection)
119
+
120
+ def call
121
+ loop do
122
+ connection.send_value(block.call(connection.receive_value))
123
+ end
124
+ end
125
+ end
126
+ private_constant :Child
69
127
  end # Worker
70
128
  end # Parallel
71
129
  end # Mutant
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ # Pipe abstraction
5
+ class Pipe
6
+ include Adamantium::Flat, Anima.new(:reader, :writer)
7
+
8
+ # Run block with pipe in binmode
9
+ #
10
+ # @return [undefined]
11
+ def self.with(io)
12
+ io.pipe(binmode: true) do |(reader, writer)|
13
+ yield new(reader: reader, writer: writer)
14
+ end
15
+ end
16
+
17
+ def self.from_io(io)
18
+ reader, writer = io.pipe(binmode: true)
19
+ new(reader: reader, writer: writer)
20
+ end
21
+
22
+ # Writer end of the pipe
23
+ #
24
+ # @return [IO]
25
+ def to_writer
26
+ reader.close
27
+ writer
28
+ end
29
+
30
+ # Parent reader end of the pipe
31
+ #
32
+ # @return [IO]
33
+ def to_reader
34
+ writer.close
35
+ reader
36
+ end
37
+
38
+ class Connection
39
+ include Anima.new(:marshal, :reader, :writer)
40
+
41
+ Error = Class.new(RuntimeError)
42
+
43
+ class Frame
44
+ include Concord.new(:io)
45
+
46
+ HEADER_FORMAT = 'N'
47
+ MAX_BYTES = (2**32).pred
48
+ HEADER_SIZE = 4
49
+
50
+ def receive_value
51
+ header = read(HEADER_SIZE)
52
+ read(Util.one(header.unpack(HEADER_FORMAT)))
53
+ end
54
+
55
+ def send_value(body)
56
+ bytesize = body.bytesize
57
+
58
+ fail Error, 'message to big' if bytesize > MAX_BYTES
59
+
60
+ io.write([bytesize].pack(HEADER_FORMAT))
61
+ io.write(body)
62
+ end
63
+
64
+ private
65
+
66
+ def read(bytes)
67
+ io.read(bytes) or fail Error, 'Unexpected EOF'
68
+ end
69
+ end
70
+
71
+ def call(payload)
72
+ send_value(payload)
73
+ receive_value
74
+ end
75
+
76
+ def receive_value
77
+ marshal.load(reader.receive_value)
78
+ end
79
+
80
+ def send_value(value)
81
+ writer.send_value(marshal.dump(value))
82
+ self
83
+ end
84
+
85
+ def self.from_pipes(marshal:, reader:, writer:)
86
+ new(
87
+ marshal: marshal,
88
+ reader: Frame.new(reader.to_reader),
89
+ writer: Frame.new(writer.to_writer)
90
+ )
91
+ end
92
+ end
93
+ end # Pipe
94
+ end # Mutant