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