• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14module TestRunner
15  ERROR_NODATA = 86
16  ERROR_CANNOT_CREATE_PROCESS = 87
17  ERROR_TIMEOUT = 88
18
19  @@plugins = []
20  @@runner_class = nil
21  @@target = 'Host'
22
23  def self.plugins
24    @@plugins
25  end
26
27  def self.runner_class=(value)
28    @@runner_class = value
29  end
30
31  def self.target
32    @@target
33  end
34
35  def self.target=(value)
36    @@target = value
37  end
38
39  class Plugin
40    def name
41      raise NotImplementedError, "#{self.class} does not implement name()."
42    end
43
44    def add_options(opts, options)
45      false
46    end
47
48    def process(options)
49      false
50    end
51  end
52
53  def self.log(level, *args)
54    puts args if level <= $VERBOSITY
55  end
56
57  def self.print_exception(exception)
58    puts "Exception: exception class   : #{exception.class}"
59    puts "           exception message : #{exception.message}"
60    exception.backtrace.each do |t|
61      puts "                       trace : #{t}"
62    end
63  end
64
65  def self.split_separated_by_colon(string)
66    string.split(':')
67          .drop(1)
68          .flat_map { |s| s.split(',').map(&:strip) }
69  end
70
71  def self.create_runner(file, id, reporter_factory, root_dir, report_dir)
72    @@runner_class.new(
73          file, id, reporter_factory, root_dir, report_dir)
74  end
75
76  class CommandRunner
77    def initialize(command, reporter)
78      @command = command
79      @reporter = reporter
80    end
81
82    def dump_output(t, output_err, output)
83      start = Time.now
84
85      while (Time.now - start) <= $TIMEOUT
86        Kernel.select([output_err], nil, nil, 1)
87        begin
88          output << output_err.read_nonblock(2048)
89        rescue IO::WaitReadable
90        rescue EOFError
91          return true # finished normally
92        rescue StandardError => e
93          output << "\nUnexpected exception when reading from pipe: #{e.class.name}, #{e.message}"
94          break
95        end
96      end
97      !t.alive? # finished on timeout
98    end
99
100    def start_process_timeout
101      input, output_err, t = if $enable_core
102                               Open3.popen2e(@command, pgroup: true)
103                             else
104                               Open3.popen2e(@command, pgroup: true, rlimit_core: 0)
105                             end
106      pid = t[:pid]
107      output = ''
108      finished = dump_output t, output_err, output
109
110      input.close
111      output_err.close
112
113      unless finished
114        output << "\nProcess hangs for #{$TIMEOUT}s '#{@command}'" \
115                  "\nKilling pid:#{pid}"
116        begin
117          Process.kill('-TERM', Process.getpgid(pid))
118        rescue Errno::ESRCH
119        rescue Exception => e
120          TestRunner.print_exception e
121        end
122        return output.strip, ERROR_TIMEOUT, false
123      end
124
125      if t.value.exited?
126        exitstatus = t.value.exitstatus
127      elsif t.value.signaled?
128        output << t.value.inspect
129        exitstatus = 128 + t.value.termsig
130      else
131        output << t.value.inspect
132        exitstatus = 254 # fallback exit code for an unexpected abnormal exit
133      end
134
135      [output.strip, exitstatus, t.value.coredump?]
136    rescue Errno::ENOENT => e
137      ["Failed to start #{@command} - no executable", ERROR_CANNOT_CREATE_PROCESS, false]
138    rescue StandardError
139      ["Failed to start #{@command}", ERROR_CANNOT_CREATE_PROCESS, false]
140    end
141
142    def run_cmd
143      @reporter.log_start_command @command
144      start_process_timeout
145    end
146  end # Runner
147end # module
148