lifull-dev/bucky-core

View on GitHub
lib/bucky/core/test_core/test_manager.rb

Summary

Maintainability
A
0 mins
Test Coverage
C
73%
# frozen_string_literal: true

require 'json'
require_relative '../test_core/test_case_loader'
require_relative '../../utils/config'
require_relative './test_class_generator'
require_relative '../test_core/exit_handler'

module Bucky
  module Core
    module TestCore
      module ParallelHelper
        parent_pid = Process.pid

        # Terminate parent and child process when getting interrupt signal
        Signal.trap('INT') do
          Process.kill('TERM', -1 * parent_pid)
        end

        private

        def parallel_new_worker_each(data_set, max_processes, &block)
          # Max parallel workers number
          available_workers = max_processes

          # If child process dead, available workers increase
          Signal.trap('CLD') { available_workers += 1 }

          r_pipe, w_pipe = IO.pipe

          data_set.each do |data|
            # Wait until worker is available and handle exit code from previous process
            unless available_workers.positive?
              Process.wait
              Bucky::Core::TestCore::ExitHandler.instance.raise unless $CHILD_STATUS.exitstatus.zero?
            end
            # Workers decrease when start working
            available_workers -= 1
            fork { block.call(data, w_pipe) }
          end
          # Handle all exit code in waitall
          Process.waitall.each do |child|
            Bucky::Core::TestCore::ExitHandler.instance.raise unless child[1].exitstatus.zero?
          end

          w_pipe.close
          results_set = collect_results_set(r_pipe)
          r_pipe.close

          results_set
        end

        def parallel_distribute_into_workers(data_set, max_processes, &block)
          # Group the data by remainder of index
          data_set_grouped = data_set.group_by.with_index { |_elem, index| index % max_processes }
          r_pipe, w_pipe = IO.pipe
          # Use 'values' method to get only hash's key into an array
          data_set_grouped.values.each do |data_for_pre_worker|
            # Number of child process is equal to max_processes (or equal to data_set length when data_set length is less than max_processes)
            fork do
              data_for_pre_worker.each { |data| block.call(data, w_pipe) }
            end
          end
          # Handle all exit code in waitall
          Process.waitall.each do |child|
            Bucky::Core::TestCore::ExitHandler.instance.raise unless child[1].exitstatus.zero?
          end

          w_pipe.close
          results_set = collect_results_set(r_pipe)
          r_pipe.close

          results_set
        end

        def collect_results_set(r_pipe)
          results_set = {}
          r_pipe.each_line do |line|
            r = JSON.parse(line)
            results_set[r['test_class_name']] = r
          end

          results_set
        end
      end

      class TestManager
        include ParallelHelper

        # Keep test conditions and round number
        def initialize(test_cond)
          @test_cond = test_cond
          @re_test_count = @test_cond[:re_test_count]
          @tdo = Bucky::Core::Database::TestDataOperator.new
          @start_time = Time.now
          $job_id = @tdo.save_job_record_and_get_job_id(@start_time, @test_cond[:command_and_option], @test_cond[:base_fqdn])
          @json_report = {
            summary: {
              cases_count: 0,
              success_count: 0,
              failure_count: 0,
              job_id: $job_id,
              test_category: test_cond[:test_category],
              device: test_cond[:device],
              labels: test_cond[:label],
              exclude_labels: test_cond[:xlabel],
              rerun_job_id: test_cond[:job],
              round_count: 0
            }
          }
        end

        def run
          execute_test
        end

        # Rerun by job id
        def rerun
          rerun_job_id = @test_cond[:job]
          $round = @tdo.get_last_round_from_job_id(rerun_job_id)
          @test_cond[:re_test_cond] = @tdo.get_ng_test_cases_at_last_execution(
            is_error: 1, job_id: rerun_job_id, round: $round
          )
          execute_test
        end

        private

        # Load test suite from test code.
        def load_test_suites
          test_suite_data = Bucky::Core::TestCore::TestCaseLoader.load_testcode(@test_cond)
          raise StandardError, "\nThere is no test case!\nPlease check test condition." if test_suite_data.empty?

          @tdo.update_test_suites_data(test_suite_data)
          @tdo.add_suite_id_to_loaded_suite_data(test_suite_data)
        end

        # Generate and execute test
        def do_test_suites(test_suite_data)
          # For checking on linkstatus
          e2e_parallel_num = Bucky::Utils::Config.instance[:e2e_parallel_num]
          linkstatus_parallel_num = Bucky::Utils::Config.instance[:linkstatus_parallel_num]
          tcg = Bucky::Core::TestCore::TestClassGenerator.new(@test_cond)
          case @test_cond[:test_category]
          when 'e2e' then results_set = parallel_new_worker_each(test_suite_data, e2e_parallel_num) { |data, w_pipe| tcg.generate_test_class(data: data, w_pipe: w_pipe) }
          when 'linkstatus' then
            linkstatus_url_log = {}
            results_set = parallel_distribute_into_workers(test_suite_data, linkstatus_parallel_num) { |data, w_pipe| tcg.generate_test_class(data: data, linkstatus_url_log: linkstatus_url_log, w_pipe: w_pipe) }
          end

          results_set
        end

        def execute_test
          all_round_results = []
          @re_test_count.times do |i|
            Bucky::Core::TestCore::ExitHandler.instance.reset
            $round = i + 1
            @json_report[:summary][:round_count] = $round
            test_suite_data = load_test_suites
            all_round_results.append(do_test_suites(test_suite_data))
            @test_cond[:re_test_cond] = @tdo.get_ng_test_cases_at_last_execution(
              is_error: 1, job_id: $job_id, round: $round
            )
            break if @test_cond[:re_test_cond].empty?
          end

          return unless @test_cond[:out]

          @json_report[:summary][:cases_count] = all_round_results[0].sum { |_case, res| res['cases_count'] }
          @json_report[:summary][:failure_count] = all_round_results[-1].sum { |_case, res| res['failure_count'] }
          @json_report[:summary][:success_count] = @json_report[:summary][:cases_count] - @json_report[:summary][:failure_count]

          File.open(@test_cond[:out], 'w') do |f|
            f.puts(@json_report.to_json)
            puts "\nSave report : #{@test_cond[:out]}\n"
          end
        end
      end
    end
  end
end