-1

I'm trying to test a gem I'm creating with RSpec. The gem's purpose is to create queues (using 'bunny'). It will serve to communicate between processes on several servers.

But I cannot find documentation on how to safely create processes inside RSpec running environment without spawning several testing processes (all displaying example failures and successes).

Here is what I wanted the tests to do :

  • Spawn children processes, waiting on the queue
  • Push messages from the main RSpec process
  • Consumes the queue on the children processes
  • Wait for children to stop and get the number of messages received from each child.

For now I implemented a simple case where child is consuming only one message and then stops.

Here is my code currently :

module Queues
  # Basic CR accepting only jobs of type cmd_line
  class CR
    attr_reader :nb_jobs
    def initialize
      # opening communication pipes
      @rout, @wout = IO.pipe
      @nb_jobs = nil # not yet available.
    end
    def main
      @todo = JobPipe.instance
      job = @todo.pop do |j|
        # accept only CMD_LINE type of jobs.
        j.type == Messages::Job::CMD_LINE
      end
      # run command
      %x{#{job.cmd}}
      @wout.puts "1" # saying that we did one job
    end
    def run
      @pid = Process.fork
      if @pid.nil? then
        # we are in the child
        self.main
        @rout.close
        @wout.close
        exit
      end
    end
    def wait
      @nb_jobs = @rout.gets(nil).to_i
      Process.wait(@pid)
      @rout.close
      @wout.close
      @nb_jobs
    end
  end

  @job = Messages::Job.new({:type => Messages::Job::CMD_LINE, :cmd => "sleep 1" })

  RSpec.describe JobPipe do
    context "one Orchestrator and one CR" do
      before(:each) do
        indalo_queue_pre_configure
      end

      it "can send a job with Orchestrator and be received by CR" do
        cr = CR.new
        cr.run # execute the C.R. process
        todo = JobPipe.instance
        todo.push(@job)
        nb_jobs = cr.wait
        expect(nb_jobs).to eql(1)
      end
    end

    context "one Orchestrator and severals CR" do
      it 'can send one job per CR and get all back' do
        crs = Array.new(rand(2..10)) { CR.new }
        crs.each do |cr|
          cr.run
        end
        todo = JobPipe.instance
        crs.each do |_|
          todo.push(@job)
        end
        nb_jobs = 0
        crs.each do |cr|
          nb_jobs += cr.wait
        end
        expect(nb_jobs).to eql(crs.length)
      end
    end
  end

end

Edit: The question is (sorry not putting it right away, this was a mistake):

Is there a way to use correctly RSpec on a multi-process environment ?

I'm not looking for a code review, just wanted to display a clear example of what I wanted to do. Here I used fork, but this duplicate all the process (including RSpec part) and got numerous RSpec outputs which is not what we would expect in a test suite.

I would expect that only the main program states the RSpec outputs/stats and the subprocesses just interact with it.

The only way I see to do that correctly is not fork, but call subprocesses through an other mean. Maybe I answer alone to this question...

But not knowing well RSpec, I was wondering if someone knew how to do it within RSpec without writing external code. It seems to me that having separate codes linked to a single test example is not a good idea.

What I found about multi-process testing is this plugin to RSpec. The only thing is I don't know about the mock concept, but maybe I have to learn about it...

shamox
  • 178
  • 1
  • 5
  • So, what is your question? – DiegoSalazar Oct 09 '17 at 18:36
  • Welcome to stackoverflow! =) Stackoverflow is a unique site in that it is only about questions and answers. It seems like you've got the beginnings of a good program here, but it's unclear what you're asking. When you post a question it should contain a very clear question, which yours does not currently. People may mis-interpret this as you asking the community to write code for you: which is definitely frowned upon. If you're simply looking for some feedback on your program you might want to try https://codereview.stackexchange.com/ . Welcome again! – kingsfoil Oct 09 '17 at 18:50
  • I edited my question, hoping it's clearer. – shamox Oct 10 '17 at 07:38

1 Answers1

0

Ok, I found an answer which is to use the &block argument of the Process.fork method. In this case, you don't really duplicate all the process, but just execute the block of code in an other process and then return 0 (like said in the Ruby doc).

This prevent the children to get all the RSpec environment and displaying plenty of times the states of your tests.

PS : Be careful not to forget to redirect STDOUT/STDERR of child process if you don't want them to pollute the STDOUT/STDERR of the test.

PS2: don't forget to close @wout on the parent side if you call @rout.gets(nil) in it, because having it opened on the parent prevent EOF from happening (a bug in the code I presented) even if you close it in the child.

PS3: Use two pipes instead of one to prevent child/parent to talk and listen in the same. Childhood error but I did it again.

PS4: Use exit statement (at the end of the &block) to prevent zombie state of the child and usure parent not waiting too long that the rest of the child process dies.

Sorry for that long post, but it's good it stays also for me ^^

shamox
  • 178
  • 1
  • 5